diff options
author | Bill MacAllister <whm@stanford.edu> | 2016-08-14 00:56:44 -0700 |
---|---|---|
committer | Bill MacAllister <whm@stanford.edu> | 2016-08-14 00:56:44 -0700 |
commit | 18225a1b837a0132ffbbf428b0516dfd949fdb98 (patch) | |
tree | db2422d95c38d7e0cc9c1fe7cdcdb692d95c040a | |
parent | 31b242637975e2c237983d3c97c01693cce4a608 (diff) | |
parent | 58f98fcb42c5b48c1686839f955a304bf2b844ca (diff) |
Merge tag 'upstream/3.0.4'
Upstream version 3.0.4
43 files changed, 2384 insertions, 65 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad560f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +blib/ +/.build/ +_build/ +cover_db/ +inc/ +Build +!Build/ +Build.bat +.last_cover_stats +Makefile +Makefile.old +MANIFEST.bak +META.json +MYMETA.* +nytprof.out +pm_to_blib +*.o +*.bs +*.gcov +*.gcda +*.gcno +*~ +LDAPapi.c +constant.h @@ -1,4 +1,21 @@ Revision history for Perl5 Net::LDAPapi Module. +3.0.4 Mon Nov 30 19:00:00 PST 2015 + - Fix undef comparison + - Misc variable initializations to quiet warnings + - Fixed sasl mechanisms initializtion + - Examples cleanup + - LDAPv3 extended operation support + - New developer mode test suite + - Fixed quanah/net-ldapapi#3: ldap_set_rebind_proc XS being called with invalid arguments from set_rebind_proc + - Fixed quanah/net-ldapapi#6: ldap_sasl_bind has wrong prototype in LDAPapi.xs + - Fixed quanah/net-ldapapi#8: search_s() clobbers ATTRS parameter + - Fixed quanah/net-ldapapi#11: result() blocking when called with output from rename() + - Fixed quanah/net-ldapapi#20: ldap_result() doesn't honour passed timeout value + - Fixed quanah/net-ldapapi#21: ldap_set_option(LDAP_OPT_TIMEOUT, 1) on OpenLDAP returns -1 + - Fixed quanah/net-ldapapi#28: Server control responses get eaten after a NULL character in the berval + - Fixed quanah/net-ldapapi#30: ldap_search_ext() and ldap_search_ext_s() segfault when used with timeout + - Fixed quanah/net-ldapapi#31: ldap_result() and ldap_url_search_st() timeout parameters have a granularity of 1 second + - Fixed quanah/net-ldapapi#40: Server control requests get eaten after a NULL character in the berval 3.0.3 Wed Aug 20 12:23:00 PST 2008 - Add Convert::ASN1 requirement. - Fix error code handling (CPAN bug#35910) @@ -31,9 +31,13 @@ Howard Chu <hyc@symas.com> - For getting this project started again Marcus Watts - For submitting patches to the new code line +Phillip O'Donnell <podonnell@cpan.org> + - Extended operation support + - Behaviour driven development test suite + - Bugfixes -- Quanah Gibson-Mount -mishikal@yahoo.com +quanah.gibsonmount@gmail.com CPAN: /by-authors/id/M/MI/MISHIKAL/ -- Clayton Donley @@ -4,6 +4,7 @@ use strict; use Carp; use Convert::ASN1; use vars qw($VERSION @ISA @EXPORT $AUTOLOAD); +no warnings "uninitialized"; require Exporter; require DynaLoader; @@ -21,6 +22,8 @@ require AutoLoader; ldap_rename ldap_rename_s ldap_compare_ext ldap_compare_ext_s ldap_delete_ext ldap_delete_ext_s ldap_search_ext ldap_search_ext_s ldap_result + ldap_extended_operation ldap_extended_operation_s ldap_parse_extended_result + ldap_parse_whoami ldap_whoami ldap_whoami_s ldap_msgfree ldap_msg_free ldap_msgid ldap_msgtype ldap_get_lderrno ldap_set_lderrno ldap_parse_result ldap_err2string ldap_count_entries ldap_first_entry ldap_next_entry ldap_get_dn @@ -159,6 +162,8 @@ require AutoLoader; LDAP_OPT_SSL LDAP_OPT_THREAD_FN_PTRS LDAP_OPT_TIMELIMIT + LDAP_OPT_TIMEOUT + LDAP_OPT_NETWORK_TIMEOUT LDAP_OTHER LDAP_PARAM_ERROR LDAP_PARTIAL_RESULTS @@ -227,7 +232,7 @@ require AutoLoader; LDAP_TAG_IM_RES_VALUE LDAP_TAG_SASL_RES_CREDS ); -$VERSION = '3.0.3'; +$VERSION = '3.0.4'; sub AUTOLOAD { # This AUTOLOAD is used to 'autoload' constants from the constant() @@ -482,7 +487,7 @@ sub bind_s { my ($self, @args) = @_; - my ($saslmech, $status, $servercredp, $sctrls, $cctrls); + my ($status, $servercredp, $sctrls, $cctrls); my ($dn, $pass, $authtype, $serverctrls, $clientctrls) = $self->rearrange(['DN', 'PASSWORD', 'TYPE', 'SCTRLS', 'CCTRLS'], @args); @@ -499,7 +504,7 @@ sub bind_s if ($authtype == $self->LDAP_AUTH_SASL) { $status = ldap_sasl_interactive_bind_s($self->{"ld"}, $dn, $pass, - $sctrls, $cctrls, $saslmech, + $sctrls, $cctrls, $self->{"saslmech"}, $self->{"saslrealm"}, $self->{"saslauthzid"}, $self->{"saslsecprops"}, @@ -837,6 +842,10 @@ sub next_changed_entries { @entries = (); + if ($self->{'status'} == 0) { # ldap_result return 0 = timeout + return @entries; + } + $asn = $self->{"asn"}; while( $msg = $self->result_message ) { @@ -1114,6 +1123,31 @@ sub parse_result { return %result; } # end of parse_result(...) +sub parse_extended_result { + my ($self, @args) = @_; + + my ($msg, $freeMsg) = $self->rearrange(['MSG', 'FREEMSG'], @args); + + my ($status, %result); + + $freeMsg = 0 unless $freeMsg; + $msg = $self->{"msg"} unless $msg; + + my ($retoidp, $retdatap); + + $status = + ldap_parse_extended_result($self->{"ld"}, $msg, $retoidp, $retdatap, $freeMsg); + + $self->errorize($status); + if( $status != $self->LDAP_SUCCESS ) { + return undef; + } + + $result{"retoidp"} = $retoidp; + $result{"retdatap"} = $retdatap; + + return %result; +} # end of parse_extended_result(...) # needs docs bellow in POD. XXX sub parse_intermediate { @@ -1147,6 +1181,27 @@ sub parse_intermediate { return %result; } # end of parse_result(...) +sub parse_whoami { + my ($self, @args) = @_; + + my ($msg) = $self->rearrange(['MSG'], @args); + + my ($status, %result); + + $msg = $self->{"msg"} unless $msg; + + my ($authzid); + + $status = + ldap_parse_whoami($self->{"ld"}, $msg, $authzid); + + $self->errorize($status); + if( $status != $self->LDAP_SUCCESS ) { + return undef; + } + + return $authzid; +} # end of parse_whoami(...) sub perror { @@ -1478,12 +1533,12 @@ sub listen_for_changes $self->rearrange(['BASEDN', 'SCOPE', 'FILTER', 'ATTRS', 'ATTRSONLY', 'TIMEOUT', 'SIZELIMIT', 'COOKIE'], @args); - croak("No Filter Specified") if ($filter eq ""); + croak("No Filter Specified") if (!defined($filter)); croak("No cookie file specified") unless $cookie; $self->{"cookie"} = $cookie; - if( $attrs == undef ) { + if( !defined($attrs) ) { my @null_array = (); $attrs = \@null_array; } @@ -1542,9 +1597,9 @@ sub search 'SIZELIMIT'], @args); - croak("No Filter Specified") if ($filter eq ""); + croak("No Filter Specified") if (!defined($filter)); - if( $attrs == undef ) { + if( !defined($attrs) ) { my @null_array = (); $attrs = \@null_array; } @@ -1592,7 +1647,7 @@ sub search_s croak("No Filter Passed as Argument 3") if ($filter eq ""); - if( $attrs == undef ) { + if( !defined($attrs) ) { my @null_array = (); $attrs = \@null_array; } @@ -1623,6 +1678,110 @@ sub search_ext_s return $self->search_s(@args); } # end of search_ext_s +sub extended_operation +{ + my ($self, @args) = @_; + my ($msgid, $status, $sctrls, $cctrls); + + my ($oid, $berval, $serverctrls, $clientctrls) = + $self->rearrange(['OID', 'BERVAL', + 'SCTRLS', 'CCTRLS'], + @args); + + $sctrls = $self->create_controls_array(@$serverctrls) if $serverctrls; + $cctrls = $self->create_controls_array(@$clientctrls) if $clientctrls; + + $status = ldap_extended_operation($self->{"ld"}, $oid, $berval, length($berval), + $sctrls, $cctrls, + $msgid); + + ldap_controls_array_free($sctrls) if $sctrls; + ldap_controls_array_free($cctrls) if $cctrls; + + $self->errorize($status); + if( $status != $self->LDAP_SUCCESS ) { + return undef; + } + + return $msgid; +} # end of extended_operation + +sub extended_operation_s +{ + my ($self, @args) = @_; + my ($status, $retoidp, $retdatap, $sctrls, $cctrls); + + my ($oid, $berval, $serverctrls, $clientctrls, $result) = + $self->rearrange(['OID', 'BERVAL', + 'SCTRLS', 'CCTRLS', 'RESULT'], + @args); + + $sctrls = $self->create_controls_array(@$serverctrls) if $serverctrls; + $cctrls = $self->create_controls_array(@$clientctrls) if $clientctrls; + + $status = ldap_extended_operation_s($self->{"ld"}, $oid, $berval, length($berval), + $sctrls, $cctrls, + $retoidp, $retdatap); + + ldap_controls_array_free($sctrls) if $sctrls; + ldap_controls_array_free($cctrls) if $cctrls; + + $self->errorize($status); + + $result->{'retoidp'} = $retoidp; + $result->{'retdatap'} = $retdatap; + + return $status; +} # end of extended_operation_s + +sub whoami +{ + my ($self, @args) = @_; + my ($msgid, $status, $sctrls, $cctrls); + + my ($serverctrls, $clientctrls) = + $self->rearrange(['SCTRLS', 'CCTRLS'], + @args); + + $sctrls = $self->create_controls_array(@$serverctrls) if $serverctrls; + $cctrls = $self->create_controls_array(@$clientctrls) if $clientctrls; + + $status = ldap_whoami($self->{"ld"}, $sctrls, $cctrls, $msgid); + + ldap_controls_array_free($sctrls) if $sctrls; + ldap_controls_array_free($cctrls) if $cctrls; + + $self->errorize($status); + if( $status != $self->LDAP_SUCCESS ) { + return undef; + } + + return $msgid; +} # end of whoami + +sub whoami_s +{ + my ($self, @args) = @_; + my ($status, $authzidOut, $sctrls, $cctrls); + + my ($authzid, $serverctrls, $clientctrls) = + $self->rearrange(['AUTHZID', 'SCTRLS', 'CCTRLS'], + @args); + + $sctrls = $self->create_controls_array(@$serverctrls) if $serverctrls; + $cctrls = $self->create_controls_array(@$clientctrls) if $clientctrls; + + $status = ldap_whoami_s($self->{"ld"}, $authzidOut, $sctrls, $cctrls); + + ldap_controls_array_free($sctrls) if $sctrls; + ldap_controls_array_free($cctrls) if $cctrls; + + $self->errorize($status); + + $$authzid = $authzidOut; + + return $status; +} # end of whoami_s sub count_references { @@ -1668,10 +1827,10 @@ sub set_rebind_proc my ($self, @args) = @_; my ($status); - my ($rebindproc) = $self->rearrange(['REBINDPROC'], @args); + my ($rebindproc, $params) = $self->rearrange(['REBINDPROC', 'PARAMS'], @args); if( ref($rebindproc) eq "CODE" ) { - $status = ldap_set_rebind_proc($self->{"ld"}, $rebindproc); + $status = ldap_set_rebind_proc($self->{"ld"}, $rebindproc, $params); } else { croak("REBINDPROC is not a CODE Reference"); } @@ -1877,7 +2036,7 @@ sub create_control croak("No OID of controls is passed") unless $oid; croak("No BerVal is passed") unless $berval; - $critical = 1 if $critical == undef; + $critical = 1 if !defined($critical); my ($ctrl) = undef; my $status = ldap_create_control($oid, $berval, length($berval), $critical, $ctrl); @@ -2287,7 +2446,26 @@ Net::LDAPapi - Perl5 Module Supporting LDAP API return($dn,$pass,LDAP_AUTH_SIMPLE); } - +=head1 EXTENDED OPERATIONS + + Extended operations are supported. + + The extended_operation and extended_operation_s methods are used to + invoke extended operations. + + Example (WHOAMI): + + %result = (); + + if ($ld->extended_operation_s(-oid => "1.3.6.1.4.1.4203.1.11.3", -result => \%result) != LDAP_SUCCESS) + { + $ld->perror("ldap_extended_operation_s"); + exit -1; + } + + Note that WHOAMI is already natively implemented via whoami and whoami_s + methods. + =head1 SUPPORTED METHODS =over 4 @@ -2455,6 +2633,27 @@ Net::LDAPapi - Perl5 Module Supporting LDAP API @components = $ld->explode_rdn($rdn, 0); +=item extended_operation OID BERVAL SCTRLS CCTRLS + + Asynchronous method for invoking an extended operation. + + Returns a non-negative MSGID upon success. + + Examples: + + $msgid = $ld->extended_operation("1.3.6.1.4.1.4203.1.11.3"); + +=item extended_operation_s OID BERVAL SCTRLS CCTRLS RESULT + + Synchronous method for invoking an extended operation. + + Returns LDAP_SUCCESS upon success. + + Examples: + + $status = $ld->extended_operation_s(-oid => "1.3.6.1.4.1.4203.1.11.3", \ + -result => \%result); + =item first_attribute Returns pointer to first attribute name found in the current entry. @@ -2907,6 +3106,26 @@ Net::LDAPapi - Perl5 Module Supporting LDAP API $status = $ld->url_search_s($my_ldap_url,0,2); +=item whoami SCTRLS CCTRLS + + Asynchronous method for invoking an LDAP whoami extended operation. + + Returns a non-negative MSGID upon success. + + Examples: + + $msgid = $ld->whoami(); + +=item whoami_s AUTHZID SCTRLS CCTRLS + + Synchronous method for invoking an LDAP whoami extended operation. + + Returns LDAP_SUCCESS upon success. + + Examples: + + $status = $ld->whoami_s(\$authzid); + =back =head1 AUTHOR @@ -334,6 +334,32 @@ ldap_b2_interact(LDAP *ld, unsigned flags, void *def, void *inter) return LDAP_SUCCESS; } +static struct timeval * +sv2timeval(SV *data) +{ + struct timeval *tv = NULL; + + if (SvPOK(data)) + { + /* set the NV flag if it's readable as a double */ + SvNV(data); + } + + if (SvIOK(data) || SvNOK(data)) { + Newx(tv, 1, struct timeval); + + tv->tv_sec = SvIV(data); + tv->tv_usec = ((SvNV(data) - SvIV(data))*1000000); + } + + return tv; +} + +static SV * +timeval2sv(struct timeval *data) +{ + return newSVnv(data->tv_sec + ((double)data->tv_usec / 1000000)); +} MODULE = Net::LDAPapi PACKAGE = Net::LDAPapi @@ -384,10 +410,41 @@ int ldap_set_option(ld,option,optdata) LDAP * ld int option - int optdata + SV * optdata CODE: { - RETVAL = ldap_set_option(ld,option,&optdata); + void *optptr = NULL; + + bool must_safefree = 0; + + int sv_i; + + switch(option) + { +#ifdef OPENLDAP + case LDAP_OPT_TIMEOUT: + case LDAP_OPT_NETWORK_TIMEOUT: + optptr = (void *) sv2timeval(optdata); + must_safefree = 1; + + break; +#endif + default: + if (SvIOK(optdata)) + { + sv_i = SvIV(optdata); + optptr = (void *) &sv_i; + } + + break; + } + + RETVAL = ldap_set_option(ld,option,optptr); + + if (must_safefree) + { + Safefree(optptr); + } } OUTPUT: RETVAL @@ -396,10 +453,26 @@ int ldap_get_option(ld,option,optdata) LDAP * ld int option - int optdata = NO_INIT + SV * optdata CODE: { - RETVAL = ldap_get_option(ld, option, &optdata); + void *data = NULL; + + RETVAL = ldap_get_option(ld, option, &data); + + switch(option) + { +#ifdef OPENLDAP + case LDAP_OPT_TIMEOUT: + case LDAP_OPT_NETWORK_TIMEOUT: + sv_setsv(SvRV(optdata), timeval2sv(data)); + break; +#endif + default: + sv_setiv(SvRV(optdata), (long)data); + break; + } + } OUTPUT: RETVAL @@ -472,7 +545,7 @@ ldap_add_ext_s(ld,dn,ldap_change_ref,sctrls,cctrls) Safefree(ldap_change_ref); int -ldap_sasl_bind(ld, dn, passwd, sctrls, serverctrls, clientctrls, msgidp) +ldap_sasl_bind(ld, dn, passwd, serverctrls, clientctrls, msgidp) LDAP * ld LDAP_CHAR * dn LDAP_CHAR * passwd @@ -540,6 +613,7 @@ ldap_rename(ld, dn, newrdn, newSuperior, deleteoldrdn, sctrls, cctrls, msgidp) } OUTPUT: RETVAL + msgidp int ldap_rename_s(ld, dn, newrdn, newSuperior, deleteoldrdn, sctrls, cctrls) @@ -621,7 +695,7 @@ ldap_search_ext(ld, base, scope, filter, attrs, attrsonly, sctrls, cctrls, timeo int attrsonly LDAPControl ** sctrls LDAPControl ** cctrls - struct timeval * timeout + SV * timeout int sizelimit int msgidp = NO_INIT @@ -630,6 +704,7 @@ ldap_search_ext(ld, base, scope, filter, attrs, attrsonly, sctrls, cctrls, timeo char **attrs_char; SV **current; int arraylen,count; + struct timeval *tv_timeout = NULL; if (SvTYPE(SvRV(attrs)) != SVt_PVAV) { @@ -650,9 +725,13 @@ ldap_search_ext(ld, base, scope, filter, attrs, attrsonly, sctrls, cctrls, timeo } attrs_char[arraylen+1] = NULL; } + + tv_timeout = sv2timeval(timeout); + RETVAL = ldap_search_ext(ld, base, scope, filter, attrs_char, - attrsonly, sctrls, cctrls, timeout, sizelimit, + attrsonly, sctrls, cctrls, tv_timeout, sizelimit, &msgidp); + Safefree(tv_timeout); Safefree(attrs_char); } OUTPUT: @@ -669,7 +748,7 @@ ldap_search_ext_s(ld, base, scope, filter, attrs, attrsonly, sctrls, cctrls, tim int attrsonly LDAPControl ** sctrls LDAPControl ** cctrls - struct timeval * timeout + SV * timeout int sizelimit LDAPMessage * res = NO_INIT CODE: @@ -677,6 +756,7 @@ ldap_search_ext_s(ld, base, scope, filter, attrs, attrsonly, sctrls, cctrls, tim char **attrs_char; SV **current; int arraylen,count; + struct timeval *tv_timeout = NULL; if (SvTYPE(SvRV(attrs)) == SVt_PVAV) { @@ -697,31 +777,138 @@ ldap_search_ext_s(ld, base, scope, filter, attrs, attrsonly, sctrls, cctrls, tim croak("Net::LDAPapi::ldap_search_ext_s needs ARRAY reference as argument 5."); XSRETURN(1); } - RETVAL = ldap_search_ext_s(ld,base,scope,filter,attrs_char,attrsonly,sctrls,cctrls,timeout,sizelimit,&res); + + tv_timeout = sv2timeval(timeout); + + RETVAL = ldap_search_ext_s(ld,base,scope,filter,attrs_char,attrsonly,sctrls,cctrls,tv_timeout,sizelimit,&res); + + Safefree(tv_timeout); Safefree(attrs_char); } OUTPUT: RETVAL res +int +ldap_extended_operation(ld, oid, bv_val, bv_len, sctrls, cctrls, msgidp) + LDAP * ld + LDAP_CHAR * oid + LDAP_CHAR * bv_val + int bv_len + LDAPControl ** sctrls + LDAPControl ** cctrls + int msgidp = NO_INIT + + CODE: + { + struct berval indata; + + if (bv_len == 0) { + RETVAL = ldap_extended_operation(ld, oid, NULL, + sctrls, cctrls, + &msgidp); + } else { + indata.bv_val = bv_val; + indata.bv_len = bv_len; + + RETVAL = ldap_extended_operation(ld, oid, &indata, + sctrls, cctrls, + &msgidp); + } + } + OUTPUT: + RETVAL + msgidp + +int +ldap_extended_operation_s(ld, oid, bv_val, bv_len, sctrls, cctrls, retoidp, retdatap) + LDAP * ld + LDAP_CHAR * oid + LDAP_CHAR * bv_val + int bv_len + LDAPControl ** sctrls + LDAPControl ** cctrls + char * retoidp = NO_INIT + char * retdatap = NO_INIT + CODE: + { + struct berval indata, *retdata; + + if (bv_len == 0) { + RETVAL = ldap_extended_operation_s(ld, oid, NULL, + sctrls, cctrls, + &retoidp, &retdata); + } else { + indata.bv_val = bv_val; + indata.bv_len = bv_len; + + RETVAL = ldap_extended_operation_s(ld, oid, &indata, + sctrls, cctrls, + &retoidp, &retdata); + } + + if (retdata != NULL) + retdatap = ldap_strdup(retdata->bv_val); + + ber_memfree(retdata); + } + OUTPUT: + RETVAL + retoidp + retdatap + +int +ldap_whoami(ld, sctrls, cctrls, msgidp) + LDAP * ld + LDAPControl ** sctrls + LDAPControl ** cctrls + int msgidp = NO_INIT + + CODE: + { + RETVAL = ldap_whoami(ld, sctrls, cctrls, + &msgidp); + } + OUTPUT: + RETVAL + msgidp + +int +ldap_whoami_s(ld, authzid, sctrls, cctrls) + LDAP * ld + LDAPControl ** sctrls + LDAPControl ** cctrls + char * authzid = NO_INIT + CODE: + { + struct berval *retdata; + + RETVAL = ldap_whoami_s(ld, &retdata, sctrls, cctrls); + + if (retdata != NULL) + authzid = ldap_strdup(retdata->bv_val); + + ber_memfree(retdata); + } + OUTPUT: + RETVAL + authzid int ldap_result(ld, msgid, all, timeout, result) LDAP * ld int msgid int all - LDAP_CHAR * timeout + SV * timeout LDAPMessage * result = NO_INIT CODE: { - struct timeval *tv_timeout = NULL, timeoutbuf; - if (atof(timeout) > 0 && timeout && *timeout) - { - tv_timeout = &timeoutbuf; - tv_timeout->tv_sec = atof(timeout); - tv_timeout->tv_usec = 0; - } - RETVAL = ldap_result(ld, msgid, all, NULL, &result); + struct timeval *tv_timeout = NULL; + + tv_timeout = sv2timeval(timeout); + + RETVAL = ldap_result(ld, msgid, all, tv_timeout, &result); + Safefree(tv_timeout); } OUTPUT: RETVAL @@ -951,6 +1138,33 @@ ldap_parse_result(ld, msg, errorcodep, matcheddnp, errmsgp, referrals_ref, serve errmsgp int +ldap_parse_extended_result(ld, msg, retoidp, retdatap, freeit) + LDAP * ld + LDAPMessage * msg + char * retoidp = NO_INIT + char * retdatap = NO_INIT + int freeit + CODE: + { + struct berval *retdata; + + retdata = ber_memalloc(sizeof(struct berval *)); + + RETVAL = + ldap_parse_extended_result(ld, msg, &retoidp, + &retdata, freeit); + + if (retdata != NULL) + retdatap = ldap_strdup(retdata->bv_val); + + ber_memfree(retdata); + } + OUTPUT: + RETVAL + retoidp + retdatap + +int ldap_parse_intermediate(ld, msg, retoidp, retdatap, serverctrls_ref, freeit) LDAP * ld LDAPMessage * msg @@ -1002,6 +1216,28 @@ ldap_parse_intermediate(ld, msg, retoidp, retdatap, serverctrls_ref, freeit) retoidp retdatap +int +ldap_parse_whoami(ld, msg, authzid) + LDAP * ld + LDAPMessage * msg + char * authzid = NO_INIT + CODE: + { + struct berval *retdata; + + retdata = ber_memalloc(sizeof(struct berval *)); + + RETVAL = + ldap_parse_whoami(ld, msg, &retdata); + + if (retdata != NULL) + authzid = ldap_strdup(retdata->bv_val); + + ber_memfree(retdata); + } + OUTPUT: + RETVAL + authzid char * ldap_control_oid(control) @@ -1014,12 +1250,12 @@ ldap_control_oid(control) RETVAL -char * +SV * ldap_control_berval(control) LDAPControl * control CODE: { - RETVAL = control->ldctl_value.bv_val; + RETVAL = newSVpv(control->ldctl_value.bv_val, control->ldctl_value.bv_len); } OUTPUT: RETVAL @@ -1436,18 +1672,16 @@ ldap_url_search_st(ld,url,attrsonly,timeout,result) LDAP * ld char * url int attrsonly - LDAP_CHAR * timeout + SV * timeout LDAPMessage * result = NO_INIT CODE: { - struct timeval *tv_timeout = NULL, timeoutbuf; - if (timeout && *timeout) - { - tv_timeout = &timeoutbuf; - tv_timeout->tv_sec = atof(timeout); - tv_timeout->tv_usec = 0; - } + struct timeval *tv_timeout = NULL; + + tv_timeout = sv2timeval(timeout); + RETVAL = ldap_url_search_st(ld,url,attrsonly,tv_timeout,&result); + Safefree(tv_timeout); } OUTPUT: RETVAL @@ -1629,8 +1863,7 @@ ldap_create_control(oid, bv_val, bv_len, iscritical, ctrlp) LDAPControl *ctrl = malloc(sizeof(LDAPControl)); ctrl->ldctl_oid = ber_strdup(oid); - ctrl->ldctl_value.bv_val = ber_strdup(bv_val); - ctrl->ldctl_value.bv_len = bv_len; + ber_mem2bv(bv_val, bv_len, 1, &ctrl->ldctl_value); ctrl->ldctl_iscritical = iscritical; ctrlp = ctrl; @@ -18,3 +18,30 @@ examples/updatepw.pl examples/web500.pl examples/www-ldap.pl META.yml Module meta-data (added by MakeMaker) +t/features/whoami.feature +t/features/search.feature +t/features/options.feature +t/features/step_definitions/rename_steps.pl +t/features/step_definitions/modify_steps.pl +t/features/step_definitions/extended_operation_steps.pl +t/features/step_definitions/server_controls_steps.pl +t/features/step_definitions/options_steps.pl +t/features/step_definitions/delete_steps.pl +t/features/step_definitions/search_steps.pl +t/features/step_definitions/add_steps.pl +t/features/step_definitions/whoami_steps.pl +t/features/step_definitions/general_steps.pl +t/features/step_definitions/bind_steps.pl +t/features/step_definitions/syncrepl_steps.pl +t/features/step_definitions/compare_steps.pl +t/features/compare.feature +t/features/modify.feature +t/features/syncrepl.feature +t/features/rename.feature +t/features/add.feature +t/features/server_controls.feature +t/features/extended_operations.feature +t/features/bind.feature +t/features/delete.feature +t/01-bdd-cucumber.t +t/test-config.pl diff --git a/META.yml b/META.yml deleted file mode 100644 index 6526d4b..0000000 --- a/META.yml +++ /dev/null @@ -1,11 +0,0 @@ -# http://module-build.sourceforge.net/META-spec.html -#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# -name: Net-LDAPapi -version: 3.0.3 -version_from: LDAPapi.pm -installdirs: site -requires: - Convert::ASN1: 0.19 - -distribution_type: module -generated_by: ExtUtils::MakeMaker version 6.17 diff --git a/Makefile.PL b/Makefile.PL index fa36724..fcb8f8c 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -128,4 +128,14 @@ WriteMakefile( )), 'depend' => { 'LDAPapi.c' => 'constant.h' }, 'clean' => { 'FILES' => 'constant.h' }, + META_MERGE => { + 'meta-spec' => { version => 2 }, + resources => { + repository => { + type => 'git', + url => 'https://github.com/quanah/net-ldapapi.git', + web => 'https://github.com/quanah/net-ldapapi', + }, + }, + }, ); @@ -195,14 +195,17 @@ Perl-OO style interface if you have never used the C API. FEEDBACK ======== - Any feedback should be directed to mishikal@yahoo.com + Any feedback should be directed to quanah.gibsonmount@gmail.com + BUGS ==== -The non-OO stuff should work well. Please let me know if I've introduced + The non-OO stuff should work well. Please let me know if I've introduced any bugs in the OO stuff or the changed examples. + Please report bugs at github - https://github.com/quanah/net-ldapapi/issues/ + -- Clayton Donley Rolling Meadows, IL, USA @@ -215,8 +218,8 @@ Chief Architect, Symas Corporation http://www.symas.com Core Team, OpenLDAP Project http://www.openldap.org Quanah Gibson-Mount -email: mishikal@yahoo.com +email: quanah.gibsonmount@gmail.com CPAN: /by-authors/id/M/MI/MISHIKAL -Principal Software Engineer -Zimbra, Inc http://www.zimbra.com +Platform Architect +Synacor, Inc http://www.zimbra.com http://synacor.com/ Core Team, OpenLDAP Project http://www.openldap.org diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7d9add --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +The Net::LDAPapi Perl Module uses the OpenLDAP and Mozilla C api's to directly access and manipulate an LDAP v2 or LDAP v3 server. diff --git a/examples/ldapwalk-dieter.pl b/examples/ldapwalk-dieter.pl new file mode 100755 index 0000000..e3321be --- /dev/null +++ b/examples/ldapwalk-dieter.pl @@ -0,0 +1,176 @@ +#!/usr/bin/perl +# +# $Id: ldapwalk.pl,v 1.1 2007/07/01 20:14:40 dieter Exp dieter $ +# ldapwalk.pl - Walks through Records Matching a Given Filter +# Author: Clayton Donley, Motorola, <donley@cig.mot.com> +# +# Demonstration of Synchronous Searching in PERL5. +# +# Rather than printing attribute and values directly, they are +# stored in a Hash, where further manipulation would be very simple. +# The output could then be printed to a file or standard output, or +# simply run through the modify or add commands. +# +# Usage: ldapwalk.pl FILTER +# Example: ldapwalk.pl "sn=Donley" +# + +use strict; +use Net::LDAPapi; + +# Define these values + +my $ldap_server = "localhost"; +my $BASEDN = "dc=example, dc=com"; +my $sizelimit = 100; # Set to Maximum Number of Entries to Return + # Can set small to test error routines +my $deref = "search"; + +# Various Variable Declarations... +my $ld; +my $dn; +my $attr; +my $ent; +my $ber; +my @vals; +my %record; +my $rc; +my $result; + +# +# Initialize Connection to LDAP Server + +if (($ld = new Net::LDAPapi($ldap_server)) == -1) +{ + die "Connection Failed!"; +} + +#ldap_set_option(0,LDAP_OPT_DEBUG_LEVEL,-1); + +# +# Bind as NULL User to LDAP connection $ld + +$ld->sasl_parms(-mech=>"DIGEST-MD5",-flags=>LDAP_SASL_AUTOMATIC); + +if ($ld->bind_s("benchmark","xxx",LDAP_AUTH_SASL) != LDAP_SUCCESS) +# if ($ld->bind_s != LDAP_SUCCESS) +{ + $ld->unbind; + die "bind: ", $ld->errstring, ": ", $ld->extramsg; +} +# +# This will set the size limit to $sizelimit from above. The command +# is a Netscape addition, but I've programmed replacement versions for +# other APIs. +$ld->set_option(LDAP_OPT_SIZELIMIT,$sizelimit); +# $ld->set_option(LDAP_OPT_DEREF,$deref); + +# This routine is COMPLETELY unnecessary in this application, since +# the rebind procedure at the end of this program simply rebinds as +# a NULL user. +#$ld->set_rebind_proc(&rebindproc); + +# +# Specify Search Filter and List of Attributes to Return + +my $filter = $ARGV[0]; +my @attrs = ("cn","mail","telephonenumber"); + +# +# Perform Search +my $msgid = $ld->search($BASEDN,LDAP_SCOPE_ONELEVEL,$filter,\@attrs,0); + +if ($msgid < 0) +{ + $ld->unbind; + die "search: ", $ld->errstring, ": ", $ld->extramsg; +} + +# Reset Number of Entries Counter +my $nentries = 0; + +# Set no timeout. +my $timeout = -1; + +# +# Cycle Through Entries +while (($rc = $ld->result($msgid,0,$timeout)) == LDAP_RES_SEARCH_ENTRY) +{ + $nentries++; + + for ($ent = $ld->first_entry; $ent != 0; $ent = $ld->next_entry) + { + +# +# Get Full DN + + if (($dn = $ld->get_dn) eq "") + { + $ld->unbind; + die "get_dn: ", $ld->errstring, ": ", $ld->extramsg; + } + +# +# Cycle Through Each Attribute + + for ($attr = $ld->first_attribute; $attr ne ""; $attr = $ld->next_attribute) + { + +# +# Notice that we're using get_values_len. This will retrieve binary +# as well as text data. You can change to get_values to only get text +# data. +# + @vals = $ld->get_values ($attr); + $record{$dn}->{$attr} = [@vals]; + } + } + $ld->msgfree; + +} +if ($rc == LDAP_RES_SEARCH_RESULT && + $ld->err != LDAP_SUCCESS) +{ + $ld->unbind; + die "result: ", $ld->errstring, ": ", $ld->extramsg; +} + +print "Found $nentries records\n"; + +$ld->unbind; + +foreach $dn (keys %record) +{ + my $item; + print "dn: $dn\n"; + foreach $attr (keys %{$record{$dn}}) + { + for $item ( @{$record{$dn}{$attr}}) + { + if ($attr =~ /binary/ ) + { + print "$attr: <binary>\n"; + } elsif ($attr eq "jpegphoto") { +# +# Notice how easy it is to take a binary attribute and dump it to a file +# or such. Gotta love PERL. +# + print "$attr: JpegPhoto (length: " . length($item). ")\n"; + open (TEST,">$dn.jpg"); + print TEST $item; + close (TEST); + } else { + print "$attr: $item\n"; + } + } + } +} + +exit; + +sub rebindproc +{ + + return("","",LDAP_AUTH_SIMPLE); +} + diff --git a/examples/ldapwalk.pl b/examples/ldapwalk.pl index 3cfbbbe..91b1a86 100755 --- a/examples/ldapwalk.pl +++ b/examples/ldapwalk.pl @@ -40,7 +40,7 @@ my $result; if (($ld = new Net::LDAPapi($ldap_server)) == -1) { - die "Connection Failed!"; + die "Unable to initialize!"; } #ldap_set_option(0,LDAP_OPT_DEBUG_LEVEL,-1); @@ -53,8 +53,9 @@ if (($ld = new Net::LDAPapi($ldap_server)) == -1) #if ($ld->bind_s("tester","tester",LDAP_AUTH_SASL) != LDAP_SUCCESS) if ($ld->bind_s != LDAP_SUCCESS) { + my $errstr=$ld->errstring; $ld->unbind; - die "bind: ", $ld->errstring, ": ", $ld->extramsg; + die "bind: ", $errstr; } # This will set the size limit to $sizelimit from above. The command @@ -114,7 +115,7 @@ while (1) # # Cycle Through Each Attribute - for ($attr = $ld->first_attribute; $attr ne ""; $attr = $ld->next_attribute) + for ($attr = $ld->first_attribute; defined($attr); $attr = $ld->next_attribute) { # @@ -129,7 +130,7 @@ while (1) $ld->msgfree; } -if ( $result == undef && $ld->err != LDAP_SUCCESS) +if ( !defined($result) && $ld->err != LDAP_SUCCESS) { $ld->unbind; die "result: ", $ld->errstring, ": ", $ld->extramsg; @@ -164,6 +165,7 @@ foreach $dn (keys %record) } } } + print "\n"; } exit; diff --git a/examples/ldapwalk2.pl b/examples/ldapwalk2.pl index 759e31d..eb1665a 100755 --- a/examples/ldapwalk2.pl +++ b/examples/ldapwalk2.pl @@ -36,7 +36,7 @@ my $attr; if (($ldcon = new Net::LDAPapi($ldap_server)) == -1) { - die "Unable to Open LDAP Connection"; + die "Unable to initialize!"; } if ($ldcon->bind_s != LDAP_SUCCESS) diff --git a/examples/testurl.pl b/examples/testurl.pl new file mode 100755 index 0000000..f6d9960 --- /dev/null +++ b/examples/testurl.pl @@ -0,0 +1,83 @@ +#!/usr/bin/perl -w +# +# testwrite.pl - Test of LDAP URL Operations in Perl5 +# Author: Clayton Donley <donley@cig.mot.com> +# +# This script tests some of the basic LDAP URL functions. +# Call the script with an LDAP URL to perform a search. + +use strict; +use Net::LDAPapi; + +my $urlhref; +my $url = $ARGV[0] || "ldap://ldap.four11.com/??sub?(cn=Clayton Donley)"; + +if (ldap_is_ldap_url($url)) +{ + $urlhref = ldap_url_parse($url); +} else { + die "$url: Not an LDAP Url."; +} + +if ($urlhref) +{ + print "host: " . $urlhref->{'host'} . "\n"; + print "port: " . $urlhref->{'port'} . "\n"; + print "base: " . $urlhref->{'dn'} . "\n"; + + my $attr; + foreach $attr (@{$urlhref->{'attr'}}) + { + print "attr: " . $attr . "\n"; + } + print "filter: " . $urlhref->{'filter'} . "\n"; + print "scope: " . $urlhref->{'scope'} . "\n"; + +# If using Netscape, there is an options key specifying the use of SSL, etc... + + if ($urlhref->{'options'}) + { + print "options: " . $urlhref->{'options'} . "\n" + } + + print "Connecting...\n"; + + my $port = $urlhref->{"port"} || 389; + my $ld = new Net::LDAPapi(-host=>$urlhref->{"host"},-port=>$port); + + if ($ld == -1) + { + die "Connection failed..."; + } + + $ld->bind_s; + + $ld->url_search_s($url,0); + + my %record = %{$ld->get_all_entries}; + + $ld->unbind; + + my @dns = (sort keys %record); + print $#dns+1 . " entries returned.\n"; + + foreach my $dn (@dns) + { + print "dn: $dn\n"; + foreach my $attr (keys %{$record{$dn}}) + { + foreach my $item (@{$record{$dn}{$attr}}) + { + if ($attr =~ /binary/) + { + print "$attr: binary - length=" . length($item) . "\n"; + } else { + print "$attr: $item\n"; + } + } + } + } + +} else { + print "Invalid LDAP URL: $url\n"; +} diff --git a/t/01-bdd-cucumber.t b/t/01-bdd-cucumber.t new file mode 100644 index 0000000..87babd3 --- /dev/null +++ b/t/01-bdd-cucumber.t @@ -0,0 +1,37 @@ +#!/usr/bin/perl + +BEGIN { + require './t/test-config.pl'; + if (!$RunDeveloperTests) { + print "1..0 # Skipped: Developer tests are not enabled"; + + exit; + } +}; + + +use strict; +use warnings; +use Devel::Cover; +use Test::More; + +# This will find step definitions and feature files in the directory you point +# it at below +use Test::BDD::Cucumber::Loader; + +# This harness prints out nice TAP +use Test::BDD::Cucumber::Harness::TestBuilder; + +# Load a directory with Cucumber files in it. It will recursively execute any +# file matching .*_steps.pl as a Step file, and .*\.feature as a feature file. +# The features are returned in @features, and the executor is created with the +# step definitions loaded. +my ( $executor, @features ) = Test::BDD::Cucumber::Loader->load( + 't/features/' ); + +# Create a Harness to execute against. TestBuilder harness prints TAP +my $harness = Test::BDD::Cucumber::Harness::TestBuilder->new({}); + +# For each feature found, execute it, using the Harness to print results +$executor->execute( $_, $harness ) for @features; +done_testing(); diff --git a/t/features/add.feature b/t/features/add.feature new file mode 100644 index 0000000..c7a0d61 --- /dev/null +++ b/t/features/add.feature @@ -0,0 +1,24 @@ +Feature: Adding entries to the directory
+ As a directory consumer
+ I want to ensure that I can add entries to the directory
+ In order to store information
+
+ Background:
+ Given a usable Net::LDAPapi class
+
+ Scenario: Can add a new entry to the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've bound with default authentication to the directory
+ And a test container has been created
+ And I've added a new entry to the directory
+ Then the new entry result is LDAP_SUCCESS
+ And the test container has been deleted
+
+ Scenario: Can asynchronously add a new entry to the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've asynchronously bound with default authentication to the directory
+ And a test container has been created
+ And I've asynchronously added a new entry to the directory
+ Then after waiting for all results, the new entry result message type is LDAP_RES_ADD
+ And the new entry result is LDAP_SUCCESS
+ And the test container has been deleted
diff --git a/t/features/bind.feature b/t/features/bind.feature new file mode 100644 index 0000000..74b19be --- /dev/null +++ b/t/features/bind.feature @@ -0,0 +1,40 @@ +Feature: Binding to the directory + As a directory consumer + I want to ensure that I can bind properly to directories + In order to establish my identity + + Background: + Given a usable Net::LDAPapi class + + Scenario: Can bind anonymously + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with anonymous authentication to the directory + Then the bind result is LDAP_SUCCESS + + Scenario: Can bind with simple authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with simple authentication to the directory + Then the bind result is LDAP_SUCCESS + + Scenario: Can bind with sasl authentication + Given a Net::LDAPapi object that has been connected to the ldapi LDAP server + When I've bound with sasl authentication to the directory + Then the bind result is LDAP_SUCCESS + + Scenario: Can asynchronously bind anonymously + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've asynchronously bound with anonymous authentication to the directory + Then the bind result message type is LDAP_RES_BIND + And the bind result is LDAP_SUCCESS + + Scenario: Can asynchronously bind with simple authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've asynchronously bound with simple authentication to the directory + Then the bind result message type is LDAP_RES_BIND + And the bind result is LDAP_SUCCESS + +# Scenario: Can asynchronously bind with sasl authentication +# Given a Net::LDAPapi object that has been connected to the ldapi LDAP server +# When I've asynchronously bound with sasl authentication to the directory +# Then the bind result message type is LDAP_RES_BIND +# And the bind result is LDAP_SUCCESS diff --git a/t/features/compare.feature b/t/features/compare.feature new file mode 100644 index 0000000..f6f8406 --- /dev/null +++ b/t/features/compare.feature @@ -0,0 +1,29 @@ +Feature: Comparing values to values of attributes of entries within the directory
+ As a directory consumer
+ I want to ensure that I can test the value of an attribute on an entry within the directory
+ In order to perform simple comparisons
+
+ Background:
+ Given a usable Net::LDAPapi class
+
+ Scenario: Can compare an attribute on an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've bound with default authentication to the directory
+ And a test container has been created
+ And I've added a new entry to the directory
+ And I've compared to an attribute on the new entry
+ Then the new entry result is LDAP_SUCCESS
+ And the new entry comparison result is LDAP_COMPARE_TRUE
+ And the test container has been deleted
+
+ Scenario: Can asynchronously compare an attribute on an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've asynchronously bound with default authentication to the directory
+ And a test container has been created
+ And I've asynchronously added a new entry to the directory
+ And I've asynchronously compared to an attribute on the new entry
+ Then after waiting for all results, the new entry result message type is LDAP_RES_ADD
+ And the new entry result is LDAP_SUCCESS
+ And after waiting for all results, the new entry comparison result message type is LDAP_RES_COMPARE
+ And the new entry comparison result is LDAP_COMPARE_TRUE
+ And the test container has been deleted
diff --git a/t/features/delete.feature b/t/features/delete.feature new file mode 100644 index 0000000..663e08b --- /dev/null +++ b/t/features/delete.feature @@ -0,0 +1,29 @@ +Feature: Deleting entries from the directory
+ As a directory consumer
+ I want to ensure that I can delete entries from the directory
+ In order to remove information
+
+ Background:
+ Given a usable Net::LDAPapi class
+
+ Scenario: Can remove an entry from the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've bound with default authentication to the directory
+ And a test container has been created
+ And I've added a new entry to the directory
+ And I've deleted the new entry from the directory
+ Then the new entry result is LDAP_SUCCESS
+ And the delete entry result is LDAP_SUCCESS
+ And the test container has been deleted
+
+ Scenario: Can asynchronously remove an entry from the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've asynchronously bound with default authentication to the directory
+ And a test container has been created
+ And I've asynchronously added a new entry to the directory
+ And I've asynchronously deleted the new entry from the directory
+ Then after waiting for all results, the new entry result message type is LDAP_RES_ADD
+ And the new entry result is LDAP_SUCCESS
+ And after waiting for all results, the delete entry result message type is LDAP_RES_DELETE
+ And the delete entry result is LDAP_SUCCESS
+ And the test container has been deleted
diff --git a/t/features/extended_operations.feature b/t/features/extended_operations.feature new file mode 100644 index 0000000..ba922f5 --- /dev/null +++ b/t/features/extended_operations.feature @@ -0,0 +1,51 @@ +Feature: Executing extended operations against the directory + As a directory consumer + I want to ensure that I can execute extended operations against the directory + In order to use arbitrary LDAPv3 extensions + + Background: + Given a usable Net::LDAPapi class + + Scenario: Can match identities retrieved with native whoami and using extended operations with anonymous authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with anonymous authentication to the directory + And I've queried the directory for my identity + And I've issued a whoami extended operation to the directory + Then the identity result is LDAP_SUCCESS + And the whoami extended operation result is LDAP_SUCCESS + And the identity matches + And the whoami extended operation matches + + Scenario: Can match identities retrieved with native whoami and using extended operations with simple authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with simple authentication to the directory + And I've queried the directory for my identity + And I've issued a whoami extended operation to the directory + Then the identity result is LDAP_SUCCESS + And the whoami extended operation result is LDAP_SUCCESS + And the identity matches + And the whoami extended operation matches + + Scenario: Can asynchronously match identities retrieved with native whoami and using extended operations with anonymous authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've asynchronously bound with anonymous authentication to the directory + And I've asynchronously queried the directory for my identity + And I've asynchronously issued a whoami extended operation to the directory + Then after waiting for all results, the identity result message type is LDAP_RES_EXTENDED + And the identity result is LDAP_SUCCESS + And after waiting for all results, the whoami extended operation result message type is LDAP_RES_EXTENDED + And the whoami extended operation result is LDAP_SUCCESS + And the identity matches + And the whoami extended operation matches + + Scenario: Can asynchronously match identities retrieved with native whoami and using extended operations with simple authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've asynchronously bound with simple authentication to the directory + And I've asynchronously queried the directory for my identity + And I've asynchronously issued a whoami extended operation to the directory + Then after waiting for all results, the identity result message type is LDAP_RES_EXTENDED + And the identity result is LDAP_SUCCESS + And after waiting for all results, the whoami extended operation result message type is LDAP_RES_EXTENDED + And the whoami extended operation result is LDAP_SUCCESS + And the identity matches + And the whoami extended operation matches diff --git a/t/features/modify.feature b/t/features/modify.feature new file mode 100644 index 0000000..38fab2a --- /dev/null +++ b/t/features/modify.feature @@ -0,0 +1,83 @@ +Feature: Updating attributes of entries within the directory
+ As a directory consumer
+ I want to ensure that I can adjust attributes on entries within the directory
+ In order to extend or update entries with new or updated information
+
+ Background:
+ Given a usable Net::LDAPapi class
+
+ Scenario: Can add a new attribute to an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've bound with default authentication to the directory
+ And a test container has been created
+ And I've added a new entry to the directory
+ And I've added a new attribute to the new entry
+ Then the new entry result is LDAP_SUCCESS
+ And the new attribute result is LDAP_SUCCESS
+ And the test container has been deleted
+
+ Scenario: Can modify an attribute on an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've bound with default authentication to the directory
+ And a test container has been created
+ And I've added a new entry to the directory
+ And I've added a new attribute to the new entry
+ And I've modified the new attribute on the new entry
+ Then the new entry result is LDAP_SUCCESS
+ And the new attribute result is LDAP_SUCCESS
+ And the modified attribute result is LDAP_SUCCESS
+ And the test container has been deleted
+
+ Scenario: Can remove an attribute on an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've bound with default authentication to the directory
+ And a test container has been created
+ And I've added a new entry to the directory
+ And I've added a new attribute to the new entry
+ And I've removed the new attribute from the new entry
+ Then the new entry result is LDAP_SUCCESS
+ And the new attribute result is LDAP_SUCCESS
+ And the removed attribute result is LDAP_SUCCESS
+ And the test container has been deleted
+
+ Scenario: Can asynchronously add a new attribute to an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've asynchronously bound with default authentication to the directory
+ And a test container has been created
+ And I've asynchronously added a new entry to the directory
+ And I've asynchronously added a new attribute to the new entry
+ Then after waiting for all results, the new entry result message type is LDAP_RES_ADD
+ And the new entry result is LDAP_SUCCESS
+ And after waiting for all results, the new attribute result message type is LDAP_RES_MODIFY
+ And the new attribute result is LDAP_SUCCESS
+ And the test container has been deleted
+
+ Scenario: Can asynchronously modify an attribute on an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've asynchronously bound with default authentication to the directory
+ And a test container has been created
+ And I've asynchronously added a new entry to the directory
+ And I've asynchronously added a new attribute to the new entry
+ And I've asynchronously modified the new attribute on the new entry
+ Then after waiting for all results, the new entry result message type is LDAP_RES_ADD
+ And the new entry result is LDAP_SUCCESS
+ And after waiting for all results, the new attribute result message type is LDAP_RES_MODIFY
+ And the new attribute result is LDAP_SUCCESS
+ And after waiting for all results, the modified attribute result message type is LDAP_RES_MODIFY
+ And the modified attribute result is LDAP_SUCCESS
+ And the test container has been deleted
+
+ Scenario: Can asynchronously remove an attribute on an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've asynchronously bound with default authentication to the directory
+ And a test container has been created
+ And I've asynchronously added a new entry to the directory
+ And I've asynchronously added a new attribute to the new entry
+ And I've asynchronously removed the new attribute from the new entry
+ Then after waiting for all results, the new entry result message type is LDAP_RES_ADD
+ And the new entry result is LDAP_SUCCESS
+ And after waiting for all results, the new attribute result message type is LDAP_RES_MODIFY
+ And the new attribute result is LDAP_SUCCESS
+ And after waiting for all results, the removed attribute result message type is LDAP_RES_MODIFY
+ And the removed attribute result is LDAP_SUCCESS
+ And the test container has been deleted
diff --git a/t/features/options.feature b/t/features/options.feature new file mode 100644 index 0000000..e432f04 --- /dev/null +++ b/t/features/options.feature @@ -0,0 +1,12 @@ +Feature: Control of options used to configure the LDAP library
+ As a directory consumer
+ I want to ensure that I can control the options that are used to configure the LDAP client library
+ In order to alter behaviour according to my needs
+
+ Background:
+ Given a usable Net::LDAPapi class
+
+ Scenario: Can set and read back options
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've set option LDAP_OPT_SIZELIMIT with value 200
+ Then option LDAP_OPT_SIZELIMIT has value 200
diff --git a/t/features/rename.feature b/t/features/rename.feature new file mode 100644 index 0000000..653dcd0 --- /dev/null +++ b/t/features/rename.feature @@ -0,0 +1,34 @@ +Feature: Renaming entries within the directory
+ As a directory consumer
+ I want to ensure that I can rename entries within the directory
+ In order to reorganise information
+
+ Background:
+ Given a usable Net::LDAPapi class
+
+ Scenario: Can rename an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've bound with default authentication to the directory
+ And a test container has been created
+ And I've added a new entry to the directory
+ And I've added a new container to the directory
+ And I've moved the new entry to the new container
+ Then the new entry result is LDAP_SUCCESS
+ And the new container result is LDAP_SUCCESS
+ And the rename entry result is LDAP_SUCCESS
+ And the test container has been deleted
+
+ Scenario: Can asynchronously rename an entry within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've asynchronously bound with default authentication to the directory
+ And a test container has been created
+ And I've asynchronously added a new entry to the directory
+ And I've asynchronously added a new container to the directory
+ And I've asynchronously moved the new entry to the new container
+ Then after waiting for all results, the new entry result message type is LDAP_RES_ADD
+ And the new entry result is LDAP_SUCCESS
+ And after waiting for all results, the new container result message type is LDAP_RES_ADD
+ And the new container result is LDAP_SUCCESS
+ And after waiting for all results, the rename entry result message type is LDAP_RES_MODDN
+ And the rename entry result is LDAP_SUCCESS
+ And the test container has been deleted
diff --git a/t/features/search.feature b/t/features/search.feature new file mode 100644 index 0000000..88f43b8 --- /dev/null +++ b/t/features/search.feature @@ -0,0 +1,77 @@ +Feature: Searching the directory + As a directory consumer + I want to ensure that I can search the directory + In order to find relevant entries + + Background: + Given a usable Net::LDAPapi class + + Scenario: Can find objects that exist within the directory + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with default authentication to the directory + And I've searched for records with scope LDAP_SCOPE_SUBTREE + Then the search result is LDAP_SUCCESS + And the search count matches + + Scenario: Can asynchronously find objects that exist within the directory + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've asynchronously bound with default authentication to the directory + And I've asynchronously searched for records with scope LDAP_SCOPE_SUBTREE + Then after waiting for all results, the search result message type is LDAP_RES_SEARCH_RESULT + And the search result is LDAP_SUCCESS + And the search count matches + + Scenario: Can find objects that exist within the directory with a timeout + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with default authentication to the directory + And I've searched for records with scope LDAP_SCOPE_SUBTREE, with timeout 1 + Then the search result is LDAP_SUCCESS + And the search count matches + + Scenario: Can asynchronously find objects that exist within the directory with a timeout + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've asynchronously bound with default authentication to the directory + And I've asynchronously searched for records with scope LDAP_SCOPE_SUBTREE, with timeout 1 + Then after waiting for all results, the search result message type is LDAP_RES_SEARCH_RESULT + And the search result is LDAP_SUCCESS + And the search count matches + + Scenario: Can find objects that exist with the directory and iterate them with next_entry and next_attribute + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with default authentication to the directory + And I've searched for records with scope LDAP_SCOPE_SUBTREE + Then the search result is LDAP_SUCCESS + And the search count matches + And using next_entry for each entry returned the dn and all attributes using next_attribute are valid + + Scenario: Can find objects that exist with the directory and iterate them with next_entry and entry_attribute + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with default authentication to the directory + And I've searched for records with scope LDAP_SCOPE_SUBTREE + Then the search result is LDAP_SUCCESS + And the search count matches + And using next_entry for each entry returned the dn and all attributes using entry_attribute are valid + + Scenario: Can find objects that exist with the directory and iterate them with result_entry and next_attribute + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with default authentication to the directory + And I've searched for records with scope LDAP_SCOPE_SUBTREE + Then the search result is LDAP_SUCCESS + And the search count matches + And using result_entry for each entry returned the dn and all attributes using next_attribute are valid + + Scenario: Can find objects that exist with the directory and iterate them with result_entry and entry_attribute + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with default authentication to the directory + And I've searched for records with scope LDAP_SCOPE_SUBTREE + Then the search result is LDAP_SUCCESS + And the search count matches + And using result_entry for each entry returned the dn and all attributes using entry_attribute are valid + + Scenario: Can find objects that exist with the directory and read them all at once + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with default authentication to the directory + And I've searched for records with scope LDAP_SCOPE_SUBTREE + Then the search result is LDAP_SUCCESS + And the search count matches + And using get_all_entries for each entry returned the dn and all attributes are valid diff --git a/t/features/server_controls.feature b/t/features/server_controls.feature new file mode 100644 index 0000000..3b4cee4 --- /dev/null +++ b/t/features/server_controls.feature @@ -0,0 +1,21 @@ +Feature: Using server controls to control results
+ As a directory consumer
+ I want to ensure that I can use server controls when querying the directory
+ In order to be able to utilise the extended features of my directory
+
+ Background:
+ Given a usable Net::LDAPapi class
+
+ Scenario: Can use the Server Side Sort control and Virtual List View Control
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ And the server side sort control definition
+ And the virtual list view control definition
+ When I've bound with default authentication to the directory
+ And I've created a server side sort control
+ And I've created a virtual list view control
+ And I've searched for records with scope LDAP_SCOPE_SUBTREE, with server controls server side sort and virtual list view
+ Then the search result is LDAP_SUCCESS
+ And the search count matches
+ And using next_entry for each entry returned the dn and all attributes using next_attribute are valid
+ And the server side sort control was successfully used
+ And the virtual list view control was successfully used
diff --git a/t/features/step_definitions/add_steps.pl b/t/features/step_definitions/add_steps.pl new file mode 100644 index 0000000..5615699 --- /dev/null +++ b/t/features/step_definitions/add_steps.pl @@ -0,0 +1,35 @@ +#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::BDD::Cucumber::StepFile;
+
+our %TestConfig = %main::TestConfig;
+
+use Data::Dumper;
+
+When qr/I've (asynchronously )?added a new (entry|container) to the directory/i, sub {
+ my $async = $1 ? 1 : 0;
+ my $type = lc($2);
+
+ S->{'new ' . $type . '_result'} = 'skipped';
+
+ return if S->{'bind_result'} eq 'skipped';
+
+ my $func = 'add_ext_s';
+ my %args = ();
+
+ if ($async) {
+ $func = 'add_ext';
+ }
+ S->{'new ' . $type . '_async'} = $async;
+
+ $args{'-dn'} = sprintf('%s,%s,%s', $TestConfig{'data'}{$type . '_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ $args{'-mod'} = $TestConfig{'data'}{$type . '_attributes'};
+
+ S->{'new ' . $type . '_result'} = S->{'object'}->$func(%args);
+};
+
+1;
diff --git a/t/features/step_definitions/bind_steps.pl b/t/features/step_definitions/bind_steps.pl new file mode 100644 index 0000000..ed9fdaa --- /dev/null +++ b/t/features/step_definitions/bind_steps.pl @@ -0,0 +1,53 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +#use Net::LDAPapi; +use Test::More; +use Test::BDD::Cucumber::StepFile; + +our %TestConfig = %main::TestConfig; + +When qr/I've (asynchronously )?bound with (.+?) authentication to the directory/, sub { + my $async = $1 ? 1 : 0; + my $type = lc($2); + + my $func = "bind_s"; + my %args = (); + + if ($async) { + $func = "bind"; + } + S->{'bind_async'} = $async; + + if ($type eq "default") { + $type = lc($TestConfig{'ldap'}{'default_bind_type'}); + } + S->{'bind_type'} = $type; + + S->{'bind_result'} = "skipped"; + + if ($type eq "anonymous") { + return if $TestConfig{'ldap'}{'bind_types'}{'anonymous'}{'enabled'} != 1; + } elsif ($type eq "simple") { + return if $TestConfig{'ldap'}{'bind_types'}{'simple'}{'enabled'} != 1; + + %args = ( + -dn => $TestConfig{'ldap'}{'bind_types'}{'simple'}{'bind_dn'}, + -password => $TestConfig{'ldap'}{'bind_types'}{'simple'}{'bind_pw'} + ); + } elsif ($type eq "sasl") { + return if $TestConfig{'ldap'}{'bind_types'}{'sasl'}{'enabled'} != 1; + + S->{'object'}->sasl_parms(%{$TestConfig{'ldap'}{'bind_types'}{'sasl'}{'sasl_parms'}}); + + %args = ( + -type => S->{'object'}->LDAP_AUTH_SASL + ); + } + + S->{'bind_result'} = S->{'object'}->$func(%args); +}; + +1; diff --git a/t/features/step_definitions/compare_steps.pl b/t/features/step_definitions/compare_steps.pl new file mode 100644 index 0000000..2b4a8c2 --- /dev/null +++ b/t/features/step_definitions/compare_steps.pl @@ -0,0 +1,36 @@ +#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::BDD::Cucumber::StepFile;
+
+our %TestConfig = %main::TestConfig;
+
+use Data::Dumper;
+
+When qr/I've (asynchronously )?compared to an attribute on the new (entry|container)/i, sub {
+ my $async = $1 ? 1 : 0;
+ my $type = lc($2);
+
+ S->{'new ' . $type . ' comparison_result'} = 'skipped';
+
+ return if S->{'bind_result'} eq 'skipped';
+
+ my $func = 'compare_ext_s';
+ my %args = ();
+
+ if ($async) {
+ $func = 'compare_ext';
+ }
+ S->{'new ' . $type . ' comparison_async'} = $async;
+
+ $args{'-dn'} = sprintf('%s,%s,%s', $TestConfig{'data'}{$type . '_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ $args{'-attr'} = $TestConfig{'compare'}{$type . '_attribute'};
+ $args{'-value'} = $TestConfig{'data'}{$type . '_attributes'}{$args{'-attr'}};
+
+ S->{'new ' . $type . ' comparison_result'} = S->{'object'}->$func(%args);
+};
+
+1;
diff --git a/t/features/step_definitions/delete_steps.pl b/t/features/step_definitions/delete_steps.pl new file mode 100644 index 0000000..41cddc6 --- /dev/null +++ b/t/features/step_definitions/delete_steps.pl @@ -0,0 +1,34 @@ +#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::BDD::Cucumber::StepFile;
+
+our %TestConfig = %main::TestConfig;
+
+use Data::Dumper;
+
+When qr/I've (asynchronously )?deleted the new (entry|container) from the directory/i, sub {
+ my $async = $1 ? 1 : 0;
+ my $type = lc($2);
+
+ S->{'delete ' . $type . '_result'} = 'skipped';
+
+ return if S->{'bind_result'} eq 'skipped';
+
+ my $func = 'delete_s';
+ my %args = ();
+
+ if ($async) {
+ $func = 'delete';
+ }
+ S->{'delete ' . $type . '_async'} = $async;
+
+ $args{'-dn'} = sprintf('%s,%s,%s', $TestConfig{'data'}{$type . '_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+
+ S->{'delete ' . $type . '_result'} = S->{'object'}->$func(%args);
+};
+
+1;
diff --git a/t/features/step_definitions/extended_operation_steps.pl b/t/features/step_definitions/extended_operation_steps.pl new file mode 100644 index 0000000..d8c7c3a --- /dev/null +++ b/t/features/step_definitions/extended_operation_steps.pl @@ -0,0 +1,59 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use Test::BDD::Cucumber::StepFile; + +our %TestConfig = %main::TestConfig; + + +When qr/I've (asynchronously )?issued a (.+?) extended operation to the directory/i, sub { + my $async = $1 ? 1 : 0; + my $extended_operation = lc($2); + + S->{$extended_operation . ' extended operation_result'} = "skipped"; + + return if S->{"bind_result"} eq "skipped"; + + my $func = "extended_operation_s"; + my %args = (); + + if ($async) { + $func = "extended_operation"; + } + S->{$extended_operation . ' extended operation_async'} = $async; + + if ($extended_operation eq "whoami") { + $args{'-oid'} = '1.3.6.1.4.1.4203.1.11.3'; + $args{'-result'} = \%{S->{'whoami extended operation_authzid'}}; + } + + S->{$extended_operation . ' extended operation_result'} = S->{'object'}->$func(%args); +}; + +Then qr/the (.+?) extended operation matches/i, sub { + my $extended_operation = lc($1); + + my $async = S->{$extended_operation . ' extended operation_async'}; + + my $got = undef; + if ($async) { + $got = {S->{'object'}->parse_extended_result(S->{$extended_operation . ' extended operation_result_id'})}; + } + + if ($extended_operation eq "whoami") { + if (!$async) { + $got = S->{$extended_operation . ' extended operation_authzid'}; + } + + is($got->{'retdatap'}, S->{'identity_got'}, 'Does ' . ($async ? 'asynchronous ' : '' ) . ' whoami extended_operation match native whoami?'); + } else { + TODO: { + todo_skip "$extended_operation matching unimplemented", 1; + } + } +}; + +1; diff --git a/t/features/step_definitions/general_steps.pl b/t/features/step_definitions/general_steps.pl new file mode 100644 index 0000000..40dbe5e --- /dev/null +++ b/t/features/step_definitions/general_steps.pl @@ -0,0 +1,104 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Net::LDAPapi; +use Test::More; +use Test::BDD::Cucumber::StepFile; + +our %TestConfig = %main::TestConfig; + +Given qr/a usable (\S+) class/, sub { use_ok($1); }; +Given qr/a Net::LDAPapi object that has been connected to the (.+?)?\s?LDAP server/, sub { + my $type = $1; + + if (!defined($type)) { + $type = $TestConfig{'ldap'}{'default_server'}; + } + + my $object = Net::LDAPapi->new(%{$TestConfig{'ldap'}{'server'}{$type}}); + + ok( $object, 'Net::LDAPapi object created'); + + S->{'object'} = $object; +}; + +When qr/a test container has been created/, sub { + my %args = (); + + $args{'-dn'} = sprintf('%s,%s', $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'}); + $args{'-mod'} = $TestConfig{'data'}{'test_container_attributes'}; + + my $status = S->{'object'}->add_s(%args); + + is(ldap_err2string($status), ldap_err2string(LDAP_SUCCESS), 'Was adding the test container successful?'); +}; + +Then qr/the test container has been deleted/, sub { + my %search_args = (); + my @delete_dns = (); + + $search_args{'-basedn'} = sprintf('%s,%s', $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'}); + $search_args{'-scope'} = LDAP_SCOPE_SUBTREE; + $search_args{'-filter'} = '(objectClass=*)'; + $search_args{'-attrs'} = ['objectClass']; + + my $search_status = S->{'object'}->search_s(%search_args); + + is(ldap_err2string($search_status), ldap_err2string(LDAP_SUCCESS), 'Was searching for the test container to delete successful?'); + + while( my $ent = S->{'object'}->result_entry) { + push(@delete_dns, S->{'object'}->get_dn()); + } + + foreach my $dn (sort { length($b) <=> length($a) } @delete_dns) { + my %delete_args = ('-dn' => $dn); + + my $status = S->{'object'}->delete_s(%delete_args); + is(ldap_err2string($status), ldap_err2string(LDAP_SUCCESS), 'Was deleting test container contents "' . $dn . '" successful?'); + } +}; + +Then qr/(after waiting for all results, )?the (.+) result message type is (.+)/, sub { + my $wait_for_all = $1 ? 1 : 0; + my $test_function = $2; + my $desired_result = $3; + + SKIP: { + + skip(C->{'scenario'}->{'name'} . " skipped", 1) if S->{$test_function . '_result'} eq "skipped"; + + isnt( S->{$test_function . '_result'}, undef, "Do we have result from $test_function?"); + + if (is( S->{$test_function . '_async'}, 1, "Was $test_function asynchronous?")) { + S->{$test_function . '_result_id'} = S->{'object'}->result(S->{$test_function . '_result'}, $wait_for_all, 1); + + is(S->{'object'}->msgtype2str(S->{'object'}->{"status"}), $desired_result, "Does expected result message type match?"); + } + + } +}; + +Then qr/the (.+) result is (.+)/, sub { + my $test_function = $1; + my $desired_result = $2; + + SKIP: { + + skip(C->{'scenario'}->{'name'} . " skipped", 1) if S->{$test_function . '_result'} eq "skipped"; + + if (isnt( S->{$test_function . '_result'}, undef, "Do we have result from $test_function?")) { + + if (S->{$test_function . '_async'}) { + my $ref = {S->{'object'}->parse_result(S->{$test_function . '_result_id'})}; + + is(ldap_err2string($ref->{'errcode'}), ldap_err2string(S->{'object'}->$desired_result), "Does expected async result code match?"); + } else { + is(ldap_err2string(S->{$test_function . '_result'}), ldap_err2string(S->{'object'}->$desired_result), "Does expected result code match?"); + } + } + } +}; + +1; diff --git a/t/features/step_definitions/modify_steps.pl b/t/features/step_definitions/modify_steps.pl new file mode 100644 index 0000000..6e6e92d --- /dev/null +++ b/t/features/step_definitions/modify_steps.pl @@ -0,0 +1,76 @@ +#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::BDD::Cucumber::StepFile;
+
+our %TestConfig = %main::TestConfig;
+
+use Data::Dumper;
+
+When qr/I've (asynchronously )?added a new attribute to the new entry/i, sub {
+ my $async = $1 ? 1 : 0;
+
+ S->{'new attribute_result'} = 'skipped';
+
+ return if S->{'bind_result'} eq 'skipped';
+
+ my $func = 'modify_ext_s';
+ my %args = ();
+
+ if ($async) {
+ $func = 'modify_ext';
+ }
+ S->{'new attribute_async'} = $async;
+
+ $args{'-dn'} = sprintf('%s,%s,%s', $TestConfig{'data'}{'entry_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ $args{'-mod'} = $TestConfig{'modify'}{'new_attribute'};
+
+ S->{'new attribute_result'} = S->{'object'}->$func(%args);
+};
+
+When qr/I've (asynchronously )?modified the new attribute on the new entry/i, sub {
+ my $async = $1 ? 1 : 0;
+
+ S->{'modified attribute_result'} = 'skipped';
+
+ return if S->{'bind_result'} eq 'skipped';
+
+ my $func = 'modify_s';
+ my %args = ();
+
+ if ($async) {
+ $func = 'modify';
+ }
+ S->{'modified attribute_async'} = $async;
+
+ $args{'-dn'} = sprintf('%s,%s,%s', $TestConfig{'data'}{'entry_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ $args{'-mod'} = $TestConfig{'modify'}{'modify_attribute'};
+
+ S->{'modified attribute_result'} = S->{'object'}->$func(%args);
+};
+
+When qr/I've (asynchronously )?removed the new attribute from the new entry/i, sub {
+ my $async = $1 ? 1 : 0;
+
+ S->{'removed attribute_result'} = 'skipped';
+
+ return if S->{'bind_result'} eq 'skipped';
+
+ my $func = 'modify_s';
+ my %args = ();
+
+ if ($async) {
+ $func = 'modify';
+ }
+ S->{'removed attribute_async'} = $async;
+
+ $args{'-dn'} = sprintf('%s,%s,%s', $TestConfig{'data'}{'entry_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ $args{'-mod'} = $TestConfig{'modify'}{'remove_attribute'};
+
+ S->{'removed attribute_result'} = S->{'object'}->$func(%args);
+};
+
+1;
diff --git a/t/features/step_definitions/options_steps.pl b/t/features/step_definitions/options_steps.pl new file mode 100644 index 0000000..43e0b6a --- /dev/null +++ b/t/features/step_definitions/options_steps.pl @@ -0,0 +1,33 @@ +#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Net::LDAPapi;
+use Test::More;
+use Test::BDD::Cucumber::StepFile;
+
+our %TestConfig = %main::TestConfig;
+
+When qr/I've set option (.+?) with value (.+)/i, sub {
+ my $option = $1;
+ my $value = int($2);
+
+ my $status = S->{'object'}->set_option(S->{'object'}->$option, $value);
+
+ is(ldap_err2string($status), ldap_err2string(LDAP_SUCCESS), "Was option successfully set?");
+};
+
+Then qr/option (.+?) has value (.+)/i, sub {
+ my $option = $1;
+ my $value = $2;
+
+ my $data;
+
+ my $status = S->{'object'}->get_option(S->{'object'}->$option, \$data);
+
+ is(ldap_err2string($status), ldap_err2string(LDAP_SUCCESS), "Was option successfully retrieved?");
+ is($data, $value, "Is the option set to the expected value?");
+};
+
+1;
diff --git a/t/features/step_definitions/rename_steps.pl b/t/features/step_definitions/rename_steps.pl new file mode 100644 index 0000000..3005c52 --- /dev/null +++ b/t/features/step_definitions/rename_steps.pl @@ -0,0 +1,35 @@ +#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::BDD::Cucumber::StepFile;
+
+our %TestConfig = %main::TestConfig;
+
+use Data::Dumper;
+
+When qr/I've (asynchronously )?moved the new entry to the new container/i, sub {
+ my $async = $1 ? 1 : 0;
+
+ S->{'rename entry_result'} = 'skipped';
+
+ return if S->{'bind_result'} eq 'skipped';
+
+ my $func = 'rename_s';
+ my %args = ();
+
+ if ($async) {
+ $func = 'rename';
+ }
+ S->{'rename entry_async'} = $async;
+
+ $args{'-dn'} = sprintf('%s,%s,%s', $TestConfig{'data'}{'entry_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ $args{'-newsuper'} = sprintf('%s,%s,%s', $TestConfig{'rename'}{'new_super'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ $args{'-newrdn'} = $TestConfig{'rename'}{'new_rdn'};
+
+ S->{'rename entry_result'} = S->{'object'}->$func(%args);
+};
+
+1;
diff --git a/t/features/step_definitions/search_steps.pl b/t/features/step_definitions/search_steps.pl new file mode 100644 index 0000000..43287a7 --- /dev/null +++ b/t/features/step_definitions/search_steps.pl @@ -0,0 +1,105 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use Test::BDD::Cucumber::StepFile; + +our %TestConfig = %main::TestConfig; +use Data::Dumper; + +When qr/I've (asynchronously )?searched for records with scope ([^, ]+)(?:, with server control(?:s)? (.+((?:(,|and) .+)*)))?(?:, with timeout (\d+))?/, sub { + my $async = $1 ? 1 : 0; + my $scope = $2; + my $timeout = $6; + + my @server_ctrls = $3 ? map { S->{'server_controls'}{$_} } split(/\s*(?:,|and)\s*/, $3) : undef; + + my $func = "search_ext_s"; + if ($async) { + $func = "search_ext"; + } + + S->{'search_async'} = $async; + + my %args = (); + + S->{'search_result'} = S->{'object'}->$func( + -basedn => $TestConfig{'ldap'}{'base_dn'}, + -scope => S->{'object'}->$scope, + -filter => $TestConfig{'search'}{'filter'}, + -attrs => \@{['cn']}, + -attrsonly => 0, + -sctrls => [@server_ctrls], + -timeout => $timeout); +}; + +Then qr/the search count matches/, sub { + is(S->{'object'}->count_entries, $TestConfig{'search'}{'count'}, "Does the search count match?"); +}; + +Then qr/using get_all_entries for each entry returned the dn and all attributes are valid/, sub { + my $entries = S->{'object'}->get_all_entries(); + + foreach my $entry_dn (keys %{$entries}) { + isnt($entry_dn, "", "Is the dn for the entry empty?"); + + foreach my $attribute (keys %{$entries->{$entry_dn}}) { + my @vals = $entries->{$entry_dn}{$attribute}; + + ok(($#vals >= 0), "Are values returned?"); + } + } +}; + +Then qr/using (.+) for each entry returned the dn and all attributes using (.+?) are valid/, sub { + my $entry_iterate_mode = lc($1); + my $attribute_iterate_mode = lc($2); + + my $attribute_tests = sub { + my $attr = shift; + my @vals = S->{'object'}->get_values($attr); + + ok(($#vals >= 0), "Are values returned?"); + + }; + + my $attribute_block = sub { + if ($attribute_iterate_mode eq "next_attribute") { + for (my $attr = S->{'object'}->first_attribute; $attr; $attr = S->{'object'}->next_attribute) { + $attribute_tests->($attr); + } + } elsif ($attribute_iterate_mode eq "entry_attribute") { + foreach my $attr (S->{'object'}->entry_attribute) { + $attribute_tests->($attr); + } + } + }; + + my $entry_tests = sub { + isnt(S->{'object'}->get_dn(), "", "Is the dn for the entry empty?"); + }; + + if ($entry_iterate_mode eq "next_entry") { + my $ent = S->{'object'}->first_entry; + + my %ent_result = S->{'object'}->parse_result(); + S->{'cache'}{'serverctrls'} = $ent_result{'serverctrls'}; + + for (; $ent; $ent = S->{'object'}->next_entry) { + $entry_tests->($ent); + + $attribute_block->($ent); + } + } elsif ($entry_iterate_mode eq "result_entry") { + foreach my $ent (S->{'object'}->result_entry) { + $entry_tests->($ent); + + $attribute_block->($ent); + } + } + +}; + +1; diff --git a/t/features/step_definitions/server_controls_steps.pl b/t/features/step_definitions/server_controls_steps.pl new file mode 100644 index 0000000..4f27e3f --- /dev/null +++ b/t/features/step_definitions/server_controls_steps.pl @@ -0,0 +1,136 @@ +#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::BDD::Cucumber::StepFile;
+
+use Net::LDAPapi;
+use Convert::ASN1;
+
+our %TestConfig = %main::TestConfig;
+
+use Data::Dumper;
+
+Given qr/the server side sort control definition/i, sub {
+ if (!defined(S->{'asn'}{'server side sort'})) {
+ S->{'asn'}{'server side sort'} = Convert::ASN1->new;
+
+ S->{'asn'}{'server side sort'}->prepare(<<ASN) or die "prepare: ", S->{'asn'}{'server side sort'}->error;
+
+ SortKey ::= SEQUENCE {
+ attributeType OCTET STRING,
+ orderingRule [0] OCTET STRING OPTIONAL,
+ reverseOrder [1] BOOLEAN }
+
+ SortKeyList ::= SEQUENCE OF SortKey
+
+ SortResult ::= SEQUENCE {
+ sortResult ENUMERATED,
+ attributeType [0] OCTET STRING OPTIONAL }
+
+ASN
+ }
+};
+
+Given qr/the virtual list view control definition/i, sub {
+ if (!defined(S->{'asn'}{'virtual list view'})) {
+ S->{'asn'}{'virtual list view'} = Convert::ASN1->new;
+
+ S->{'asn'}{'virtual list view'}->prepare(<<ASN) or die "prepare: ", S->{'asn'}{'virtual list view'}->error;
+
+ VirtualListViewRequest ::= SEQUENCE { + beforeCount INTEGER, + afterCount INTEGER, + target CHOICE { + byOffset [0] SEQUENCE { + offset INTEGER, + contentCount INTEGER + }, + greaterThanOrEqual [1] OCTET STRING + }, + contextID OCTET STRING OPTIONAL + } + + VirtualListViewResponse ::= SEQUENCE { + targetPosition INTEGER, + contentCount INTEGER, + virtualListViewResult ENUMERATED, + contextID OCTET STRING OPTIONAL + } +
+ASN
+ }
+};
+
+When qr/I've created a server side sort control/i, sub {
+ my $sss = S->{'asn'}{'server side sort'}->find('SortKeyList');
+
+ my $sss_berval = $sss->encode($TestConfig{'server_controls'}{'sss'}) or die S->{'asn'}{'server side sort'}->error;
+
+ my $sss_ctrl = S->{'object'}->create_control(
+ -oid => '1.2.840.113556.1.4.473',
+ -berval => $sss_berval,
+ );
+
+ S->{'server_controls'}{'server side sort'} = $sss_ctrl;
+};
+
+When qr/I've created a virtual list view control/i, sub {
+ my $vlv = S->{'asn'}{'virtual list view'}->find('VirtualListViewRequest');
+
+ my $vlv_berval = $vlv->encode($TestConfig{'server_controls'}{'vlv'}) or die S->{'asn'}{'virtual list view'}->error;
+
+ my $vlv_ctrl = S->{'object'}->create_control(
+ -oid => '2.16.840.1.113730.3.4.9',
+ -berval => $vlv_berval,
+ );
+
+ S->{'server_controls'}{'virtual list view'} = $vlv_ctrl;
+};
+
+Then qr/the server side sort control was successfully used/i, sub {
+ my $sss_response = S->{'asn'}{'server side sort'}->find('SortResult');
+
+ my $berval = undef;
+
+ foreach my $ctrl (@{S->{'cache'}{'serverctrls'}}) {
+ my $ctrl_oid = S->{'object'}->get_control_oid($ctrl);
+
+ if ($ctrl_oid eq '1.2.840.113556.1.4.474') {
+ $berval = S->{'object'}->get_control_berval($ctrl);
+ last;
+ }
+ }
+
+ isnt($berval, undef, "Was a berval returned?");
+
+ my $result = $sss_response->decode($berval) || ok(0, $sss_response->error);
+
+ is(ldap_err2string($result->{'sortResult'}), ldap_err2string(LDAP_SUCCESS), "Does server side sort result code match?");
+};
+
+Then qr/the virtual list view control was successfully used/i, sub {
+ my $vlv_response = S->{'asn'}{'virtual list view'}->find('VirtualListViewResponse');
+
+ my $berval = undef;
+
+ foreach my $ctrl (@{S->{'cache'}{'serverctrls'}}) {
+ my $ctrl_oid = S->{'object'}->get_control_oid($ctrl);
+
+ if ($ctrl_oid eq '2.16.840.1.113730.3.4.10') {
+ $berval = S->{'object'}->get_control_berval($ctrl);
+ last;
+ }
+ }
+
+ isnt($berval, undef, "Was a berval returned?");
+
+ my $result = $vlv_response->decode($berval) || ok(0, $vlv_response->error);
+
+ is(ldap_err2string($result->{'virtualListViewResult'}), ldap_err2string(LDAP_SUCCESS), "Does virtual list view result code match?");
+};
+
+
+1;
diff --git a/t/features/step_definitions/syncrepl_steps.pl b/t/features/step_definitions/syncrepl_steps.pl new file mode 100644 index 0000000..0725c32 --- /dev/null +++ b/t/features/step_definitions/syncrepl_steps.pl @@ -0,0 +1,81 @@ +#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Net::LDAPapi;
+use Test::More;
+use Test::BDD::Cucumber::StepFile;
+
+our %TestConfig = %main::TestConfig;
+
+use Data::Dumper;
+
+When qr/I've started listening for changes within the directory/i, sub {
+
+ S->{'listen changes_result'} = 'skipped';
+
+ return if S->{'bind_result'} eq 'skipped';
+
+ my $func = 'listen_for_changes';
+ my %args = ();
+
+ $args{'-basedn'} = sprintf('%s,%s', $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ $args{'-scope'} = LDAP_SCOPE_SUBTREE;
+ $args{'-filter'} = '(objectClass=*)';
+ $args{'-cookie'} = $TestConfig{'syncrepl'}{'cookie_dir'} . "syncrepl.$$.cookie";
+
+# open(COOKIE, ">" . $args{'-cookie'});
+# close(COOKIE);
+
+ S->{'listen changes_result'} = S->{'object'}->$func(%args);
+ S->{'object'}->next_changed_entries(S->{'listen changes_result'}, 0, 1);
+
+};
+
+Then qr/the changes were successfully notified/i, sub {
+ my $expected_container_dn = sprintf('%s,%s,%s', $TestConfig{'data'}{'container_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+ my $expected_entry_dn = sprintf('%s,%s,%s', $TestConfig{'data'}{'entry_dn'}, $TestConfig{'data'}{'test_container_dn'}, $TestConfig{'ldap'}{'base_dn'});
+
+ my $seen_expected = 0;
+
+ my $timeout_start = time();
+ my $timeout_length = 5;
+
+ while(!$seen_expected) {
+ if ((time() - $timeout_start) > $timeout_length) { last; }
+
+ while(my @entries = S->{'object'}->next_changed_entries(S->{'listen changes_result'}, 0, 1)) {
+ foreach my $entry (@entries) {
+
+ my $entry_dn = S->{'object'}->get_dn($entry->{'entry'});
+
+ if (lc($entry_dn) eq lc($expected_container_dn) || lc($entry_dn) eq lc($expected_entry_dn)) {
+ $seen_expected = 1;
+ last;
+ }
+ }
+ }
+ }
+
+ ok($seen_expected, 'Have we seen a notification for an expected DN?');
+
+ my %args;
+
+ my $asn = Convert::ASN1->new();
+ $asn->prepare(<<ASN);
+cancelRequestValue ::= SEQUENCE {
+ cancelID INTEGER
+}
+ASN
+
+ $args{'-oid'} = '1.3.6.1.1.8';
+ $args{'-result'} = \%{S->{'cancel_result code'}};
+ $args{'-berval'} = $asn->encode(cancelID => S->{'listen changes_result'});
+
+ my $cancel_status = S->{'object'}->extended_operation_s(%args);
+ is(ldap_err2string($cancel_status), ldap_err2string(LDAP_SUCCESS), 'Was cancelling the sync successful?');
+ S->{'object'}->{'entry'} = 0;
+};
+
+1;
diff --git a/t/features/step_definitions/whoami_steps.pl b/t/features/step_definitions/whoami_steps.pl new file mode 100644 index 0000000..e78bb79 --- /dev/null +++ b/t/features/step_definitions/whoami_steps.pl @@ -0,0 +1,65 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use Test::BDD::Cucumber::StepFile; + +our %TestConfig = %main::TestConfig; + +When qr/I\'ve (asynchronously )?queried the directory for my identity/, sub { + my $async = $1 ? 1 : 0; + + S->{'identity_authzid'} = undef; + S->{'identity_result'} = "skipped"; + + return if S->{"bind_result"} eq "skipped"; + + my $func = "whoami_s"; + my %args = (); + + if ($async) { + $func = "whoami"; + } else { + %args = ('-authzid' => \S->{'identity_authzid'}); + } + S->{'identity_async'} = $async; + + S->{'identity_result'} = S->{'object'}->$func(%args); +}; + +Then qr/the identity matches/, sub { + SKIP: { + + skip(S->{'bind_type'} . " authentication disabled in t/test-config.pl", 1) if S->{"bind_result"} eq "skipped"; + + my ($got, $expected); + + if (S->{'identity_async'}) { + $got = S->{'object'}->parse_whoami(S->{'identity_result_id'}); + } else { + $got = S->{'identity_authzid'}; + } + + S->{'identity_got'} = $got; + + if (S->{'bind_type'} eq "anonymous") { + $expected = ""; + } elsif (S->{'bind_type'} eq "simple") { + my ($attr, $value) = split(/:/, $got); + + $got = $value; + $expected = $TestConfig{'ldap'}{'bind_types'}{'simple'}{'bind_dn'}; + } elsif (S->{'bind_type'} eq "sasl") { + my ($attr, $value) = split(/:/, $got); + + $got = $value; + $expected = $TestConfig{'ldap'}{'bind_types'}{'sasl'}{'identity'}; + } + + is(lc($got), lc($expected), "Does expected identity match received identity?"); + } +}; + +1; diff --git a/t/features/syncrepl.feature b/t/features/syncrepl.feature new file mode 100644 index 0000000..3c6b9d9 --- /dev/null +++ b/t/features/syncrepl.feature @@ -0,0 +1,21 @@ +Feature: Listening for changes within the directory with syncrepl
+ As a OpenLDAP directory consumer
+ I want to ensure that I can be notified of changes to entries within the directory
+ In order to act quickly on changes
+
+ Background:
+ Given a usable Net::LDAPapi class
+
+ Scenario: Can listen for changes within the directory
+ Given a Net::LDAPapi object that has been connected to the LDAP server
+ When I've bound with default authentication to the directory
+ And a test container has been created
+ And I've started listening for changes within the directory
+ And I've added a new entry to the directory
+ And I've added a new container to the directory
+ And I've deleted the new entry from the directory
+ Then the new entry result is LDAP_SUCCESS
+ And the new container result is LDAP_SUCCESS
+ And the delete entry result is LDAP_SUCCESS
+ And the changes were successfully notified
+ And the test container has been deleted
diff --git a/t/features/whoami.feature b/t/features/whoami.feature new file mode 100644 index 0000000..892fa04 --- /dev/null +++ b/t/features/whoami.feature @@ -0,0 +1,61 @@ +Feature: Querying the directory for my identity + As a directory consumer + I want to ensure that I can retrieve my identity + In order to determine my DN when using a non-simple authentication + + Background: + Given a usable Net::LDAPapi class + + Scenario: Can query identity with anonymous authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with anonymous authentication to the directory + And I've queried the directory for my identity + Then the bind result is LDAP_SUCCESS + And the identity result is LDAP_SUCCESS + And the identity matches + + Scenario: Can query identity with simple authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've bound with simple authentication to the directory + And I've queried the directory for my identity + Then the bind result is LDAP_SUCCESS + And the identity result is LDAP_SUCCESS + And the identity matches + + Scenario: Can query identity with sasl authentication + Given a Net::LDAPapi object that has been connected to the ldapi LDAP server + When I've bound with sasl authentication to the directory + And I've queried the directory for my identity + Then the bind result is LDAP_SUCCESS + And the identity result is LDAP_SUCCESS + And the identity matches + + Scenario: Can asynchronously query identity with anonymous authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've asynchronously bound with anonymous authentication to the directory + And I've asynchronously queried the directory for my identity + Then the bind result message type is LDAP_RES_BIND + And the bind result is LDAP_SUCCESS + And after waiting for all results, the identity result message type is LDAP_RES_EXTENDED + And the identity result is LDAP_SUCCESS + And the identity matches + + Scenario: Can asynchronously query identity with simple authentication + Given a Net::LDAPapi object that has been connected to the LDAP server + When I've asynchronously bound with simple authentication to the directory + And I've asynchronously queried the directory for my identity + Then the bind result message type is LDAP_RES_BIND + And the bind result is LDAP_SUCCESS + And after waiting for all results, the identity result message type is LDAP_RES_EXTENDED + And the identity result is LDAP_SUCCESS + And the identity matches + +# Scenario: Can asynchronously query identity with sasl authentication +# Given a Net::LDAPapi object that has been connected to the ldapi LDAP server +# When I've asynchronously bound with sasl authentication to the directory +# And I've asynchronously queried the directory for my identity +# Then the bind result message type is LDAP_RES_BIND +# And the bind result is LDAP_SUCCESS +# And after waiting for all results, the identity result message type is LDAP_RES_EXTENDED +# And the identity result is LDAP_SUCCESS +# And the identity matches diff --git a/t/test-config.pl b/t/test-config.pl new file mode 100644 index 0000000..83fa8e6 --- /dev/null +++ b/t/test-config.pl @@ -0,0 +1,129 @@ +# Developer tests require: +# Test::More +# Test::BDD::Cucumber +our $RunDeveloperTests = 0; + +# Default config. +# If you're a developer of Net::LDAPapi or are likely to have multiple trees that share common test config, +# then you should override in ~/.net-ldapapi-test-config.conf (See below) +our %TestConfig = ( + 'ldap' => { + 'server' => { + 'tcp' => { + '-host' => 'localhost', + '-port' => 389, + }, + 'ldapi' => { + '-url' => 'ldapi:///', + '-debug' => 1 + } + }, + 'base_dn' => 'dc=example,dc=com', + 'bind_types' => { + 'anonymous' => { + 'enabled' => 1, + }, + 'simple' => { + 'enabled' => 1, + 'bind_dn' => 'cn=admin,dc=example,dc=com', + 'bind_pw' => 'password', + }, + 'sasl' => { + 'enabled' => 1, + 'sasl_parms' => { + '-mech' => 'EXTERNAL', + }, + 'identity' => "gidNumber=" . $< . "+uidNumber=" . (split(/ /, "$("))[0] . ",cn=peercred,cn=external,cn=auth" + } + }, + 'default_server' => 'tcp', + 'default_bind_type' => 'simple', + }, + 'search' => { + 'filter' => "sn=Last", + 'count' => 1, + }, + 'data' => { + 'test_container_attributes' => { + 'objectClass' => ['top', 'organizationalUnit'], + 'ou' => 'Test Container', + }, + 'container_attributes' => { + 'objectClass' => ['top', 'organizationalUnit'], + 'ou' => 'Test - Add Container', + }, + 'entry_attributes' => { + 'objectClass' => ['top', 'person' ,'organizationalPerson', 'inetOrgPerson'], + 'cn' => 'Test - Add Entry', + 'sn' => 'Entry', + 'givenName' => 'Test - Add', + }, + 'test_container_dn' => 'ou=Test Container', + 'container_dn' => 'ou=Test - Add Container', + 'entry_dn' => 'cn=Test - Add Entry', + }, + 'rename' => { + 'dn' => 'cn=Test - Add Entry', + 'new_rdn' => 'cn=Test - Add Entry', + 'new_super' => 'ou=Test - Add Container' + }, + 'modify' => { + 'new_attribute' => { + 'title' => { 'a' => ['New Test Title'] } + }, + 'modify_attribute' => { + 'title' => { 'r' => ['Modified Test Title'] } + }, + 'remove_attribute' => { + 'title' => '' + }, + }, + 'syncrepl' => { + 'enabled' => 1, + 'cookie_dir' => '/tmp/' + }, + 'server_controls' => { + 'sss' => [ + { + 'attributeType' => 'sn', + 'orderingRule' => '2.5.13.3', + 'reverseOrder' => 1 + }, + ], + 'vlv' => { + 'beforeCount' => 0, + 'afterCount' => 3, + 'target' => { + 'byOffset' => { + 'offset' => 1, + 'contentCount' => 0 + } + } + }, + }, + 'compare' => { + 'entry_attribute' => 'cn', + 'compare_attribute' => 'ou' + } +); + + +# Allow overrides from outside the source tree. +# This is a standard Perl file. Example below. +if ( -e $ENV{'HOME'} . '/.net-ldapapi-test-config.conf') { + require $ENV{'HOME'} . '/.net-ldapapi-test-config.conf'; +} + +1; +__END__ + +$RunDeveloperTests = 1; + +$TestConfig{'ldap'}{'base_dn'} = "o=Test Data,c=NZ"; +$TestConfig{'ldap'}{'bind_types'}{'simple'}{'bind_dn'} = "cn=admin,o=Test Data,c=NZ"; +$TestConfig{'ldap'}{'bind_types'}{'simple'}{'bind_pw'} = "password"; + +$TestConfig{'search'}{'filter'} = "sn=O'Donnell"; + + +1; @@ -6,7 +6,7 @@ # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) -BEGIN { $| = 1; print "1..8\n"; } +BEGIN { $| = 1; print "1..10\n"; } END {print "modinit - not ok\n" unless $loaded;} use Net::LDAPapi; $loaded = 1; @@ -57,6 +57,35 @@ if ($ld->bind_s != LDAP_SUCCESS) print "bind - ok\n"; ## +## ldap_whoami_s +## + +$id = ''; + +if ($ld->whoami_s(\$id) != LDAP_SUCCESS) +{ + $ld->perror("whoami_s"); + print "whoami - not ok\n"; + exit -1; +} +print "whoami - ok\n"; + +## +## ldap_extended_operation_s +## + +%result = (); + +if ($ld->extended_operation_s(-oid => "1.3.6.1.4.1.4203.1.11.3", -result => \%result) != LDAP_SUCCESS) +{ + $ld->perror("ldap_extended_operation_s"); + print "ldap_extended_operation - not ok\n"; + exit -1; +} +print "ldap_extended_operation - ok\n"; + + +## ## ldap_search_s - Synchronous Search ## @@ -122,6 +151,7 @@ print "count - ok\n"; } + ## ## Unbind LDAP Connection ## @@ -16,6 +16,7 @@ BerElement * T_PTR BerElement ** T_PTR LDAPVersion * T_PTR LDAPMod * T_PTR +LDAPMod ** T_PTR struct berval * T_PTR struct berval ** T_PTR struct timeval * T_PTR |