Implement ldap_modify_batch.

This commit is contained in:
Ondřej Hošek 2014-01-22 18:54:11 +01:00
parent 0eff7176de
commit c0e3429904
4 changed files with 588 additions and 0 deletions

View File

@ -152,6 +152,15 @@ PHP_MINIT_FUNCTION(ldap)
REGISTER_LONG_CONSTANT("LDAP_DEREF_FINDING", LDAP_DEREF_FINDING, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("LDAP_DEREF_ALWAYS", LDAP_DEREF_ALWAYS, CONST_PERSISTENT | CONST_CS);
/* Constants to be used with ldap_modify_batch() */
REGISTER_LONG_CONSTANT("LDAP_MODIFY_BATCH_ADD", LDAP_MODIFY_BATCH_ADD, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("LDAP_MODIFY_BATCH_REMOVE", LDAP_MODIFY_BATCH_REMOVE, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("LDAP_MODIFY_BATCH_REMOVE_ALL", LDAP_MODIFY_BATCH_REMOVE_ALL, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("LDAP_MODIFY_BATCH_REPLACE", LDAP_MODIFY_BATCH_REPLACE, CONST_PERSISTENT | CONST_CS);
REGISTER_STRING_CONSTANT("LDAP_MODIFY_BATCH_ATTRIB", LDAP_MODIFY_BATCH_ATTRIB, CONST_PERSISTENT | CONST_CS);
REGISTER_STRING_CONSTANT("LDAP_MODIFY_BATCH_MODTYPE", LDAP_MODIFY_BATCH_MODTYPE, CONST_PERSISTENT | CONST_CS);
REGISTER_STRING_CONSTANT("LDAP_MODIFY_BATCH_VALUES", LDAP_MODIFY_BATCH_VALUES, CONST_PERSISTENT | CONST_CS);
#if (LDAP_API_VERSION > 2000) || HAVE_NSLDAP || HAVE_ORALDAP_10
/* LDAP options */
REGISTER_LONG_CONSTANT("LDAP_OPT_DEREF", LDAP_OPT_DEREF, CONST_PERSISTENT | CONST_CS);
@ -1432,6 +1441,355 @@ PHP_FUNCTION(ldap_delete)
}
/* }}} */
/* {{{ _ldap_str_equal_to_const
*/
static int _ldap_str_equal_to_const(const char *str, uint str_len, const char *cstr)
{
int i;
if (strlen(cstr) != str_len)
return 0;
for (i = 0; i < str_len; ++i) {
if (str[i] != cstr[i]) {
return 0;
}
}
return 1;
}
/* }}} */
/* {{{ _ldap_strlen_max
*/
static int _ldap_strlen_max(const char *str, uint max_len)
{
int i;
for (i = 0; i < max_len; ++i) {
if (str[i] == '\0') {
return i;
}
}
return max_len;
}
/* }}} */
/* {{{ _ldap_hash_fetch
*/
static void _ldap_hash_fetch(zval *hashTbl, const char *key, zval **out)
{
zval **fetched;
if (zend_hash_find(Z_ARRVAL_P(hashTbl), key, strlen(key)+1, (void **) &fetched) == SUCCESS) {
*out = *fetched;
}
else {
*out = NULL;
}
}
/* }}} */
/* {{{ proto bool ldap_modify_batch(resource link, string dn, array modifs)
Perform multiple modifications as part of one operation */
PHP_FUNCTION(ldap_modify_batch)
{
ldap_linkdata *ld;
zval *link, *mods, *mod, *modinfo, *modval;
zval *attrib, *modtype, *vals;
zval **fetched;
char *dn;
int dn_len;
int i, j, k;
int num_mods, num_modprops, num_modvals;
LDAPMod **ldap_mods;
uint oper;
/*
$mods = array(
array(
"attrib" => "unicodePwd",
"modtype" => LDAP_MODIFY_BATCH_REMOVE,
"values" => array($oldpw)
),
array(
"attrib" => "unicodePwd",
"modtype" => LDAP_MODIFY_BATCH_ADD,
"values" => array($newpw)
),
array(
"attrib" => "userPrincipalName",
"modtype" => LDAP_MODIFY_BATCH_REPLACE,
"values" => array("janitor@corp.contoso.com")
),
array(
"attrib" => "userCert",
"modtype" => LDAP_MODIFY_BATCH_REMOVE_ALL
)
);
*/
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsa", &link, &dn, &dn_len, &mods) != SUCCESS) {
return;
}
ZEND_FETCH_RESOURCE(ld, ldap_linkdata *, &link, -1, "ldap link", le_link);
/* perform validation */
{
char *modkey;
uint modkeylen;
long modtype;
/* to store the wrongly-typed keys */
ulong tmpUlong;
/* make sure the DN contains no NUL bytes */
if (_ldap_strlen_max(dn, dn_len) != dn_len) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "DN must not contain NUL bytes");
RETURN_FALSE;
}
/* make sure the top level is a normal array */
zend_hash_internal_pointer_reset(Z_ARRVAL_P(mods));
if (zend_hash_get_current_key_type(Z_ARRVAL_P(mods)) != HASH_KEY_IS_LONG) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Modifications array must not be string-indexed");
RETURN_FALSE;
}
num_mods = zend_hash_num_elements(Z_ARRVAL_P(mods));
for (i = 0; i < num_mods; i++) {
/* is the numbering consecutive? */
if (zend_hash_index_find(Z_ARRVAL_P(mods), i, (void **) &fetched) != SUCCESS) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Modifications array must have consecutive indices 0, 1, ...");
RETURN_FALSE;
}
mod = *fetched;
/* is it an array? */
if (Z_TYPE_P(mod) != IS_ARRAY) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Each entry of modifications array must be an array itself");
RETURN_FALSE;
}
/* for the modification hashtable... */
zend_hash_internal_pointer_reset(Z_ARRVAL_P(mod));
num_modprops = zend_hash_num_elements(Z_ARRVAL_P(mod));
for (j = 0; j < num_modprops; j++) {
/* are the keys strings? */
if (zend_hash_get_current_key_ex(Z_ARRVAL_P(mod), &modkey, &modkeylen, &tmpUlong, 0, NULL) != HASH_KEY_IS_STRING) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Each entry of modifications array must be string-indexed");
RETURN_FALSE;
}
/* modkeylen includes the terminating NUL byte; remove that */
--modkeylen;
/* is this a valid entry? */
if (
!_ldap_str_equal_to_const(modkey, modkeylen, LDAP_MODIFY_BATCH_ATTRIB) &&
!_ldap_str_equal_to_const(modkey, modkeylen, LDAP_MODIFY_BATCH_MODTYPE) &&
!_ldap_str_equal_to_const(modkey, modkeylen, LDAP_MODIFY_BATCH_VALUES)
) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "The only allowed keys in entries of the modifications array are '" LDAP_MODIFY_BATCH_ATTRIB "', '" LDAP_MODIFY_BATCH_MODTYPE "' and '" LDAP_MODIFY_BATCH_VALUES "'");
RETURN_FALSE;
}
zend_hash_get_current_data(Z_ARRVAL_P(mod), (void **) &fetched);
modinfo = *fetched;
/* does the value type match the key? */
if (_ldap_str_equal_to_const(modkey, modkeylen, LDAP_MODIFY_BATCH_ATTRIB)) {
if (Z_TYPE_P(modinfo) != IS_STRING) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "A '" LDAP_MODIFY_BATCH_ATTRIB "' value must be a string");
RETURN_FALSE;
}
if (Z_STRLEN_P(modinfo) != _ldap_strlen_max(Z_STRVAL_P(modinfo), Z_STRLEN_P(modinfo))) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "A '" LDAP_MODIFY_BATCH_ATTRIB "' value must not contain NUL bytes");
RETURN_FALSE;
}
}
else if (_ldap_str_equal_to_const(modkey, modkeylen, LDAP_MODIFY_BATCH_MODTYPE)) {
if (Z_TYPE_P(modinfo) != IS_LONG) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "A '" LDAP_MODIFY_BATCH_MODTYPE "' value must be a long");
RETURN_FALSE;
}
/* is the value in range? */
modtype = Z_LVAL_P(modinfo);
if (
modtype != LDAP_MODIFY_BATCH_ADD &&
modtype != LDAP_MODIFY_BATCH_REMOVE &&
modtype != LDAP_MODIFY_BATCH_REPLACE &&
modtype != LDAP_MODIFY_BATCH_REMOVE_ALL
) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "The '" LDAP_MODIFY_BATCH_MODTYPE "' value must match one of the LDAP_MODIFY_BATCH_* constants");
RETURN_FALSE;
}
/* if it's REMOVE_ALL, there must not be a values array; otherwise, there must */
if (modtype == LDAP_MODIFY_BATCH_REMOVE_ALL) {
if (zend_hash_exists(Z_ARRVAL_P(mod), LDAP_MODIFY_BATCH_VALUES, strlen(LDAP_MODIFY_BATCH_VALUES) + 1)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "If '" LDAP_MODIFY_BATCH_MODTYPE "' is LDAP_MODIFY_BATCH_REMOVE_ALL, a '" LDAP_MODIFY_BATCH_VALUES "' array must not be provided");
RETURN_FALSE;
}
}
else {
if (!zend_hash_exists(Z_ARRVAL_P(mod), LDAP_MODIFY_BATCH_VALUES, strlen(LDAP_MODIFY_BATCH_VALUES) + 1)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "If '" LDAP_MODIFY_BATCH_MODTYPE "' is not LDAP_MODIFY_BATCH_REMOVE_ALL, a '" LDAP_MODIFY_BATCH_VALUES "' array must be provided");
RETURN_FALSE;
}
}
}
else if (_ldap_str_equal_to_const(modkey, modkeylen, LDAP_MODIFY_BATCH_VALUES)) {
if (Z_TYPE_P(modinfo) != IS_ARRAY) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "A '" LDAP_MODIFY_BATCH_VALUES "' value must be an array");
RETURN_FALSE;
}
/* is the array not empty? */
zend_hash_internal_pointer_reset(Z_ARRVAL_P(modinfo));
num_modvals = zend_hash_num_elements(Z_ARRVAL_P(modinfo));
if (num_modvals == 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "A '" LDAP_MODIFY_BATCH_VALUES "' array must have at least one element");
RETURN_FALSE;
}
/* are its keys integers? */
if (zend_hash_get_current_key_type(Z_ARRVAL_P(modinfo)) != HASH_KEY_IS_LONG) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "A '" LDAP_MODIFY_BATCH_VALUES "' array must not be string-indexed");
RETURN_FALSE;
}
/* are the keys consecutive? */
for (k = 0; k < num_modvals; k++) {
if (zend_hash_index_find(Z_ARRVAL_P(modinfo), k, (void **) &fetched) != SUCCESS) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "A '" LDAP_MODIFY_BATCH_VALUES "' array must have consecutive indices 0, 1, ...");
RETURN_FALSE;
}
modval = *fetched;
/* is the data element a string? */
if (Z_TYPE_P(modval) != IS_STRING) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Each element of a '" LDAP_MODIFY_BATCH_VALUES "' array must be a string");
RETURN_FALSE;
}
}
}
zend_hash_move_forward(Z_ARRVAL_P(mod));
}
}
}
/* validation was successful */
/* allocate array of modifications */
ldap_mods = safe_emalloc((num_mods+1), sizeof(LDAPMod *), 0);
/* for each modification */
for (i = 0; i < num_mods; i++) {
/* allocate the modification struct */
ldap_mods[i] = safe_emalloc(1, sizeof(LDAPMod), 0);
/* fetch the relevant data */
zend_hash_index_find(Z_ARRVAL_P(mods), i, (void **) &fetched);
mod = *fetched;
_ldap_hash_fetch(mod, LDAP_MODIFY_BATCH_ATTRIB, &attrib);
_ldap_hash_fetch(mod, LDAP_MODIFY_BATCH_MODTYPE, &modtype);
_ldap_hash_fetch(mod, LDAP_MODIFY_BATCH_VALUES, &vals);
/* map the modification type */
switch (Z_LVAL_P(modtype)) {
case LDAP_MODIFY_BATCH_ADD:
oper = LDAP_MOD_ADD;
break;
case LDAP_MODIFY_BATCH_REMOVE:
case LDAP_MODIFY_BATCH_REMOVE_ALL:
oper = LDAP_MOD_DELETE;
break;
case LDAP_MODIFY_BATCH_REPLACE:
oper = LDAP_MOD_REPLACE;
break;
default:
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unknown and uncaught modification type.");
RETURN_FALSE;
}
/* fill in the basic info */
ldap_mods[i]->mod_op = oper | LDAP_MOD_BVALUES;
ldap_mods[i]->mod_type = estrndup(Z_STRVAL_P(attrib), Z_STRLEN_P(attrib));
if (Z_LVAL_P(modtype) == LDAP_MODIFY_BATCH_REMOVE_ALL) {
/* no values */
ldap_mods[i]->mod_bvalues = NULL;
}
else {
/* allocate space for the values as part of this modification */
num_modvals = zend_hash_num_elements(Z_ARRVAL_P(vals));
ldap_mods[i]->mod_bvalues = safe_emalloc((num_modvals+1), sizeof(struct berval *), 0);
/* for each value */
for (j = 0; j < num_modvals; j++) {
/* fetch it */
zend_hash_index_find(Z_ARRVAL_P(vals), j, (void **) &fetched);
modval = *fetched;
/* allocate the data struct */
ldap_mods[i]->mod_bvalues[j] = safe_emalloc(1, sizeof(struct berval), 0);
/* fill it */
ldap_mods[i]->mod_bvalues[j]->bv_len = Z_STRLEN_P(modval);
ldap_mods[i]->mod_bvalues[j]->bv_val = estrndup(Z_STRVAL_P(modval), Z_STRLEN_P(modval));
}
/* NULL-terminate values */
ldap_mods[i]->mod_bvalues[num_modvals] = NULL;
}
}
/* NULL-terminate modifications */
ldap_mods[num_mods] = NULL;
/* perform (finally) */
if ((i = ldap_modify_ext_s(ld->link, dn, ldap_mods, NULL, NULL)) != LDAP_SUCCESS) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Batch Modify: %s", ldap_err2string(i));
RETVAL_FALSE;
} else RETVAL_TRUE;
/* clean up */
{
for (i = 0; i < num_mods; i++) {
/* attribute */
efree(ldap_mods[i]->mod_type);
if (ldap_mods[i]->mod_bvalues != NULL) {
/* each BER value */
for (j = 0; ldap_mods[i]->mod_bvalues[j] != NULL; j++) {
/* free the data bytes */
efree(ldap_mods[i]->mod_bvalues[j]->bv_val);
/* free the bvalue struct */
efree(ldap_mods[i]->mod_bvalues[j]);
}
/* the BER value array */
efree(ldap_mods[i]->mod_bvalues);
}
/* the modification */
efree(ldap_mods[i]);
}
/* the modifications array */
efree(ldap_mods);
}
}
/* }}} */
/* {{{ proto int ldap_errno(resource link)
Get the current ldap error number */
PHP_FUNCTION(ldap_errno)
@ -2516,6 +2874,12 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_modify, 0, 0, 3)
ZEND_ARG_INFO(0, entry)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_modify_batch, 0, 0, 3)
ZEND_ARG_INFO(0, link_identifier)
ZEND_ARG_INFO(0, dn)
ZEND_ARG_ARRAY_INFO(0, modifications_info, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_mod_add, 0, 0, 3)
ZEND_ARG_INFO(0, link_identifier)
ZEND_ARG_INFO(0, dn)
@ -2669,6 +3033,7 @@ const zend_function_entry ldap_functions[] = {
PHP_FE(ldap_dn2ufn, arginfo_ldap_dn2ufn)
PHP_FE(ldap_add, arginfo_ldap_add)
PHP_FE(ldap_delete, arginfo_ldap_delete)
PHP_FE(ldap_modify_batch, arginfo_ldap_modify_batch)
PHP_FALIAS(ldap_modify, ldap_mod_replace, arginfo_ldap_modify)
/* additional functions for attribute based modifications, Gerrit Thomson */

View File

@ -50,4 +50,14 @@ ZEND_END_MODULE_GLOBALS(ldap)
#define phpext_ldap_ptr ldap_module_ptr
/* Constants for ldap_modify_batch */
#define LDAP_MODIFY_BATCH_ADD 0x01
#define LDAP_MODIFY_BATCH_REMOVE 0x02
#define LDAP_MODIFY_BATCH_REMOVE_ALL 0x12
#define LDAP_MODIFY_BATCH_REPLACE 0x03
#define LDAP_MODIFY_BATCH_ATTRIB "attrib"
#define LDAP_MODIFY_BATCH_MODTYPE "modtype"
#define LDAP_MODIFY_BATCH_VALUES "values"
#endif /* PHP_LDAP_H */

View File

@ -0,0 +1,109 @@
--TEST--
ldap_modify_batch() - Basic batch modify operation
--CREDITS--
Patrick Allaert <patrickallaert@php.net>
Ondřej Hošek <ondra.hosek@gmail.com>
--SKIPIF--
<?php require_once('skipif.inc'); ?>
<?php require_once('skipifbindfailure.inc'); ?>
--FILE--
<?php
require "connect.inc";
$link = ldap_connect_and_bind($host, $port, $user, $passwd, $protocol_version);
insert_dummy_data($link);
$mods = array(
array(
"attrib" => "telephoneNumber",
"modtype" => LDAP_MODIFY_BATCH_ADD,
"values" => array(
"+1 555 5551717"
)
),
array(
"attrib" => "sn",
"modtype" => LDAP_MODIFY_BATCH_REPLACE,
"values" => array("Brown-Smith")
),
array(
"attrib" => "description",
"modtype" => LDAP_MODIFY_BATCH_REMOVE_ALL
)
);
var_dump(
ldap_modify_batch($link, "cn=userA,dc=my-domain,dc=com", $mods),
ldap_get_entries($link, ldap_search($link, "dc=my-domain,dc=com", "(sn=Brown-Smith)"))
);
?>
===DONE===
--CLEAN--
<?php
require "connect.inc";
$link = ldap_connect_and_bind($host, $port, $user, $passwd, $protocol_version);
remove_dummy_data($link);
?>
--EXPECT--
bool(true)
array(2) {
["count"]=>
int(1)
[0]=>
array(12) {
["objectclass"]=>
array(2) {
["count"]=>
int(1)
[0]=>
string(6) "person"
}
[0]=>
string(11) "objectclass"
["cn"]=>
array(2) {
["count"]=>
int(1)
[0]=>
string(5) "userA"
}
[1]=>
string(2) "cn"
["userpassword"]=>
array(2) {
["count"]=>
int(1)
[0]=>
string(4) "oops"
}
[2]=>
string(12) "userpassword"
["telephonenumber"]=>
array(3) {
["count"]=>
int(2)
[0]=>
string(14) "xx-xx-xx-xx-xx"
[1]=>
string(14) "+1 555 5551717"
}
[3]=>
string(15) "telephonenumber"
["sn"]=>
array(2) {
["count"]=>
int(1)
[0]=>
string(11) "Brown-Smith"
}
[4]=>
string(2) "sn"
["count"]=>
int(5)
["dn"]=>
string(28) "cn=userA,dc=my-domain,dc=com"
}
}
===DONE===

View File

@ -0,0 +1,104 @@
--TEST--
ldap_modify_batch() - Batch modify operations that should fail
--CREDITS--
Patrick Allaert <patrickallaert@php.net>
Ondřej Hošek <ondra.hosek@gmail.com>
--SKIPIF--
<?php require_once('skipif.inc'); ?>
<?php require_once('skipifbindfailure.inc'); ?>
--FILE--
<?php
require "connect.inc";
$link = ldap_connect_and_bind($host, $port, $user, $passwd, $protocol_version);
$addGivenName = array(
array(
"attrib" => "givenName",
"modtype" => LDAP_MODIFY_BATCH_ADD,
"values" => array("Jack")
)
);
// Too few parameters
var_dump(ldap_modify_batch());
var_dump(ldap_modify_batch($link));
var_dump(ldap_modify_batch($link, "dc=my-domain,dc=com"));
// Too many parameters
var_dump(ldap_modify_batch($link, "dc=my-domain,dc=com", $addGivenName, "Invalid additional parameter"));
// DN not found
var_dump(ldap_modify_batch($link, "dc=my-domain,dc=com", $addGivenName));
// Invalid DN
var_dump(ldap_modify_batch($link, "weirdAttribute=val", $addGivenName));
// prepare
$entry = array(
"objectClass" => array(
"top",
"dcObject",
"organization"),
"dc" => "my-domain",
"o" => "my-domain",
);
ldap_add($link, "dc=my-domain,dc=com", $entry);
// invalid domain
$mods = array(
array(
"attrib" => "dc",
"modtype" => LDAP_MODIFY_BATCH_REPLACE,
"values" => array("Wrong Domain")
)
);
var_dump(ldap_modify_batch($link, "dc=my-domain,dc=com", $mods));
// invalid attribute
$mods = array(
array(
"attrib" => "weirdAttribute",
"modtype" => LDAP_MODIFY_BATCH_ADD,
"values" => array("weirdVal", "anotherWeirdval")
)
);
var_dump(ldap_modify_batch($link, "dc=my-domain,dc=com", $mods));
?>
===DONE===
--CLEAN--
<?php
require "connect.inc";
$link = ldap_connect_and_bind($host, $port, $user, $passwd, $protocol_version);
ldap_delete($link, "dc=my-domain,dc=com");
?>
--EXPECTF--
Warning: ldap_modify_batch() expects exactly 3 parameters, 0 given in %s on line %d
NULL
Warning: ldap_modify_batch() expects exactly 3 parameters, 1 given in %s on line %d
NULL
Warning: ldap_modify_batch() expects exactly 3 parameters, 2 given in %s on line %d
NULL
Warning: ldap_modify_batch() expects exactly 3 parameters, 4 given in %s on line %d
NULL
Warning: ldap_modify_batch(): Batch Modify: No such object in %s on line %d
bool(false)
Warning: ldap_modify_batch(): Batch Modify: Invalid DN syntax in %s on line %d
bool(false)
Warning: ldap_modify_batch(): Batch Modify: Naming violation in %s on line %d
bool(false)
Warning: ldap_modify_batch(): Batch Modify: Undefined attribute type in %s on line %d
bool(false)
===DONE===