Lot of work on Portal SOAP services. Now 5 functions are exported:

* getAttributes(cookieValue)
 * getConfig()
 * getCookies(user,password)
 * error(code,language)
 * newNotification(xml)

WSDL is up to date but getConfig is not documented since it's a Lemonldap::NG internal service.
environments/ppa-mbqj77/deployments/1
Xavier Guimard 16 years ago
parent 2a77ab4066
commit 6307a00750
  1. 9
      modules/lemonldap-ng-common/lib/Lemonldap/NG/Common/BuildWSDL.pm
  2. 19
      modules/lemonldap-ng-common/lib/Lemonldap/NG/Common/CGI.pm
  3. 1
      modules/lemonldap-ng-portal/MANIFEST
  4. 33
      modules/lemonldap-ng-portal/example/index_skin.pl
  5. 132
      modules/lemonldap-ng-portal/example/scripts/buildPortalWSDL
  6. 21
      modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/SharedConf.pm
  7. 89
      modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm
  8. 163
      modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_SOAP.pm

@ -17,11 +17,16 @@ sub buildWSDL {
$portal .= "index.pl" if ( $portal =~ /\/$/ );
$xml =~ s/__PORTAL__/$portal/gs;
$xml =~ s/__DOMAIN__/$self->{conf}->{domain}/gs;
# Cookies
my @cookies = split /\s+/, $self->{conf}->{cookieName};
s#(.*)#<element name="$1" type="xsd:string"></element># foreach (@cookies);
#s#(.*)#<element name="$1" nillable="true" type="xsd:string"></element># foreach(@cookies);
$xml =~ s/__XMLCOOKIELIST__/join("\n",@cookies)/ges;
# Attributes
my @attr = (keys %{$self->{conf}->{exportedVars}},keys %{$self->{conf}->{macros}});
s#(.*)#<element name="$1" type="xsd:string" nillable="true"></element># foreach (@attr);
$xml =~ s/__ATTRLIST__/join("\n",@attr)/ges;
return $xml;
}

@ -180,7 +180,7 @@ sub startSyslog {
}
##@method protected void userLog(string mess, string level)
# 15bis) Log user access and logout.
# Log user actions on Apache logs or syslog.
# @param $mess string to log
# @param $level level of log message
sub userLog {
@ -194,21 +194,30 @@ sub userLog {
}
}
##@method void userInfo(string mess)
# Log user errors like "bad password".
# @param $mess string to log
sub userInfo {
my ( $self, $mess ) = @_;
$mess = "Lemonldap::NG portal: $mess ($ENV{REMOTE_ADDR})";
$self->userLog( $mess, 'info' );
}
##@method void userNotice(string mess)
# 15bis) Log user errors like "bad password".
# Log user actions like access and logout.
# @param $mess string to log
sub userNotice {
my ( $self, $mess ) = @_;
$mess = "Lemonldap::NG portal: $mess";
$mess = "Lemonldap::NG portal: $mess ($ENV{REMOTE_ADDR})";
$self->userLog( $mess, 'notice' );
}
##@method void userError(string mess)
# 15bis) Log user errors like "bad password".
# Log user errors like "bad password".
# @param $mess string to log
sub userError {
my ( $self, $mess ) = @_;
$mess = "Lemonldap::NG portal: $mess";
$mess = "Lemonldap::NG portal: $mess ($ENV{REMOTE_ADDR})";
$self->userLog( $mess, 'warn' );
}

@ -82,6 +82,7 @@ lib/Lemonldap/NG/Portal.pm
lib/Lemonldap/NG/Portal/_i18n.pm
lib/Lemonldap/NG/Portal/_LDAP.pm
lib/Lemonldap/NG/Portal/_Remote.pm
lib/Lemonldap/NG/Portal/_SOAP.pm
lib/Lemonldap/NG/Portal/_WebForm.pm
lib/Lemonldap/NG/Portal/AuthApache.pm
lib/Lemonldap/NG/Portal/AuthCAS.pm

@ -15,10 +15,17 @@ my $portal = Lemonldap::NG::Portal::SharedConf->new(
# ACCESS TO CONFIGURATION
# By default, Lemonldap::NG uses the default storage.conf file to know
# where to find is configuration
# where to find its configuration
# (generaly /etc/lemonldap-ng/storage.conf)
# You can specify by yourself this file :
#configStorage => { File => '/path/to/my/file' },
# or set explicitely parameters :
#configStorage => {
# Type => 'File',
# dirName => '/path/to/config/dir/'
#},
# Note that YOU HAVE TO SET configStorage here if you've declared this
# portal as SOAP configuration server in the manager
# You can also specify directly the configuration
# (see Lemonldap::NG::Handler::SharedConf(3))
@ -36,6 +43,9 @@ my $portal = Lemonldap::NG::Portal::SharedConf->new(
# Remove comment to activate SOAP Functions getCookies(user,pwd) and
# error(language, code)
#Soap => 1,
# Note that getAttibutes() will be activated but on a different URI
# (http://auth.example.com/index.pl/sessions
#exportedAttr => 'uid mail',
# PASSWORD POLICY
# Remove comment to use LDAP Password Policy
@ -52,19 +62,21 @@ my $portal = Lemonldap::NG::Portal::SharedConf->new(
# NOTIFICATIONS SERVICE
# Use it to be able to notify messages during authentication
#notification => 1,
# Note that the SOAP function newNotification will be activated on
# http://auth.example.com/index.pl/notification
# If you want to hide this, just protect "/index.pl/notification" in
# your Apache configuration file
# OTHERS
# You can also overload any parameter issued from manager
# configuration. Example:
#globalStorage => 'Lemonldap::NG::Common::Apache::Session::SOAP',
#globalStorage => 'Apache::Session::File',
#globalStorageOptions => {
# proxy => 'http://manager.example.com/soapserver.pl',
# proxyOptions => {
# timeout => 5,
# },
# # If soapserver is protected by HTTP Basic:
# User => 'http-user',
# Password => 'pass',
# 'Directory' => '/var/lib/lemonldap-ng/sessions/'
# 'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock/'
#}
# Note that YOU HAVE TO SET globalStorage here if you've declared this
# portal as SOAP session server in the manager
#},
}
);
@ -113,7 +125,7 @@ if ( $portal->process() ) {
print $portal->header('text/html; charset=utf8');
print $template->output;
}
elsif ( my $notif = $portal->notification ) {
elsif( my $notif = $portal->notification ) {
my $template = HTML::Template->new(
filename => "$skin_dir/$skin/notification.tpl",
die_on_bad_params => 0,
@ -122,7 +134,6 @@ elsif ( my $notif = $portal->notification ) {
);
$template->param( AUTH_ERROR => $portal->error );
$template->param( AUTH_ERROR_TYPE => $portal->error_type );
$template->param( AUTH_URL => $portal->get_url );
$template->param( NOTIFICATION => $notif );
print $portal->header('text/html; charset=utf8');

@ -13,50 +13,32 @@ print Lemonldap::NG::Common::BuildWSDL->new->buildWSDL(<<EOT);
__XMLCOOKIELIST__
</sequence>
</complexType>
<complexType name="AttributesSequence">
<sequence>
__ATTRLIST__
</sequence>
</complexType>
<complexType name="GetCookieResponse">
<sequence>
<element name="errorCode" type="xsd:int"></element>
<element name="cookies" type="tns1:CookiesSequence"></element>
</sequence>
</complexType>
<complexType name="GetAttributesResponse">
<sequence>
<element name="errorCode" type="xsd:int"></element>
<element name="attributes" type="tns1:AttributesSequence"></element>
</sequence>
</complexType>
</schema>
</wsdl:types>
<wsdl:message name="errorRequest">
<wsdl:part name="lang" type="xsd:string" />
<wsdl:part name="code" type="xsd:int" />
</wsdl:message>
<wsdl:message name="errorResponse">
<wsdl:part name="result" type="xsd:string" />
</wsdl:message>
<wsdl:message name="getCookiesRequest">
<wsdl:part name="user" type="xsd:string" />
<wsdl:part name="password" type="xsd:string" />
</wsdl:message>
<wsdl:message name="getCookiesResponse">
<wsdl:part name="getCookiesReturn" type="tns1:GetCookieResponse" />
</wsdl:message>
<wsdl:message name="newNotificationRequest">
<wsdl:part name="notification" type="xsd:string" />
</wsdl:message>
<wsdl:message name="newNotificationResponse">
<wsdl:part name="result" type="xsd:string" />
</wsdl:message>
<wsdl:portType name="authenticationHandler">
<wsdl:operation name="error" parameterOrder="lang code">
<wsdl:input message="impl:errorRequest" name="errorRequest" />
<wsdl:output message="impl:errorResponse" name="errorResponse" />
</wsdl:operation>
<wsdl:operation name="getCookies" parameterOrder="user password">
<wsdl:input message="impl:getCookiesRequest" name="getCookiesRequest" />
<wsdl:output message="impl:getCookiesResponse" name="getCookiesResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:portType name="notificationPostHandler">
<wsdl:operation name="newNotification" parameterOrder="notification">
<wsdl:input message="impl:newNotificationRequest" name="newNotificationRequest" />
<wsdl:output message="impl:newNotificationResponse" name="newNotificationResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:service name="authenticationHandlerService">
<wsdl:port binding="impl:authenticationSoapBinding" name="authentication">
<wsdlsoap:address location="__PORTAL__" />
</wsdl:port>
</wsdl:service>
<wsdl:binding name="authenticationSoapBinding" type="impl:authenticationHandler">
<wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="error">
@ -78,6 +60,36 @@ print Lemonldap::NG::Common::BuildWSDL->new->buildWSDL(<<EOT);
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:portType name="authenticationHandler">
<wsdl:operation name="error" parameterOrder="lang code">
<wsdl:input message="impl:errorRequest" name="errorRequest" />
<wsdl:output message="impl:errorResponse" name="errorResponse" />
</wsdl:operation>
<wsdl:operation name="getCookies" parameterOrder="user password">
<wsdl:input message="impl:getCookiesRequest" name="getCookiesRequest" />
<wsdl:output message="impl:getCookiesResponse" name="getCookiesResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:message name="errorRequest">
<wsdl:part name="lang" type="xsd:string" />
<wsdl:part name="code" type="xsd:int" />
</wsdl:message>
<wsdl:message name="errorResponse">
<wsdl:part name="result" type="xsd:string" />
</wsdl:message>
<wsdl:message name="getCookiesRequest">
<wsdl:part name="user" type="xsd:string" />
<wsdl:part name="password" type="xsd:string" />
</wsdl:message>
<wsdl:message name="getCookiesResponse">
<wsdl:part name="session" type="tns1:GetCookieResponse" />
</wsdl:message>
<wsdl:service name="notificationPostHandlerService">
<wsdl:port binding="impl:notificationPostSoapBinding" name="notificationPost">
<wsdlsoap:address location="__PORTAL__/notification" />
</wsdl:port>
</wsdl:service>
<wsdl:binding name="notificationPostSoapBinding" type="impl:notificationPostHandler">
<wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="newNotification">
@ -90,16 +102,48 @@ print Lemonldap::NG::Common::BuildWSDL->new->buildWSDL(<<EOT);
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="authenticationHandlerService">
<wsdl:port binding="impl:authenticationSoapBinding" name="authentication">
<wsdlsoap:address location="__PORTAL__" />
</wsdl:port>
</wsdl:service>
<wsdl:service name="notificationPostHandlerService">
<wsdl:port binding="impl:notificationPostSoapBinding" name="notificationPost">
<wsdlsoap:address location="__PORTAL__/notification" />
<wsdl:portType name="notificationPostHandler">
<wsdl:operation name="newNotification" parameterOrder="notification">
<wsdl:input message="impl:newNotificationRequest" name="newNotificationRequest" />
<wsdl:output message="impl:newNotificationResponse" name="newNotificationResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:message name="newNotificationRequest">
<wsdl:part name="notification" type="xsd:string" />
</wsdl:message>
<wsdl:message name="newNotificationResponse">
<wsdl:part name="result" type="xsd:string" />
</wsdl:message>
<wsdl:service name="sessionsHandlerService">
<wsdl:port binding="impl:sessionsSoapBinding" name="sessionsHandler">
<wsdlsoap:address location="__PORTAL__/sessions" />
</wsdl:port>
</wsdl:service>
<wsdl:binding name="sessionsSoapBinding" type="impl:sessionsHandler">
<wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="getAttributes">
<wsdlsoap:operation soapAction="" />
<wsdl:input name="getAttributesRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:Lemonldap/NG/Common/CGI/SOAPService" use="encoded" />
</wsdl:input>
<wsdl:output name="getAttributesResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:Lemonldap/NG/Common/CGI/SOAPService" use="encoded" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:portType name="sessionsHandler">
<wsdl:operation name="getAttributes" parameterOrder="id">
<wsdl:input message="impl:getAttributesRequest" name="getAttributesRequest" />
<wsdl:output message="impl:getAttributesResponse" name="getAttributesResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:message name="getAttributesRequest">
<wsdl:part name="id" type="xsd:string" />
</wsdl:message>
<wsdl:message name="getAttributesResponse">
<wsdl:part name="session" type="tns1:GetAttributesResponse" />
</wsdl:message>
</wsdl:definitions>
EOT

@ -34,19 +34,8 @@ sub getConf {
%args = @_;
}
%$self = ( %$self, %args );
# For better performance the Portal can use the configuration stored in
# the local file system by the handlers. This can be used when
# configuration is not local (type DBI or SOAP)
my $tmp = 0;
unless ($tmp) {
$self->{lmConf} =
Lemonldap::NG::Common::Conf->new( $self->{configStorage} )
unless $self->{lmConf};
return 0 unless ( ref( $self->{lmConf} ) );
$tmp = $self->{lmConf}->getConf;
my $tmp = $self->_getLmConf;
return 0 unless $tmp;
}
# Local configuration prepends global
$self->{$_} = $args{$_} || $tmp->{$_} foreach ( keys %$tmp );
@ -66,6 +55,14 @@ sub getProtectedSites {
return ();
}
sub _getLmConf {
my $self = shift;
$self->{lmConf} = Lemonldap::NG::Common::Conf->new( $self->{configStorage} )
unless $self->{lmConf};
return 0 unless ( ref( $self->{lmConf} ) );
return $self->{lmConf}->getConf;
}
1;
__END__

@ -160,16 +160,17 @@ sub new {
}
if ( $self->{notification}
and $ENV{PATH_INFO}
and $ENV{PATH_INFO} =~ "/notification" )
and $ENV{PATH_INFO} =~ m#^/notification# )
{
require SOAP::Lite;
$self->soapTest( 'newNotification', $self->{notifObject} );
$self->abort( 'Bad request',
'Only SOAP requests are accepted with "/notification"' );
}
if ( $self->{Soap} ) {
require SOAP::Lite;
$self->soapTest("${class}::getCookies ${class}::error");
if ( $self->{Soap} or $self->{soap} ) {
require Lemonldap::NG::Portal::_SOAP;
push @ISA, 'Lemonldap::NG::Portal::_SOAP';
$self->startSoapServices();
}
return $self;
}
@ -322,7 +323,7 @@ sub redirect {
# If $id is set to undef, return a new session.
# @param $id session reference
sub getApacheSession {
my ( $self, $id ) = @_;
my ( $self, $id, $noInfo ) = @_;
my %h;
# Trying to recover session from global session storage
@ -339,7 +340,8 @@ sub getApacheSession {
}
return 0;
}
$self->setApacheUser( $h{ $self->{whatToTrace} } ) if ($id);
$self->setApacheUser( $h{ $self->{whatToTrace} } )
if ( $id and not $noInfo );
$self->{id} = $h{_session_id};
return \%h;
}
@ -444,59 +446,6 @@ sub safe {
return $safe;
}
####################
# SOAP subroutines #
####################
=begin WSDL
_IN user $string User name
_IN password $string Password
_RETURN $getCookieResponse Response
=end WSDL
=cut
##@method SOAP::Data getCookies(string user,string password)
# Called in SOAP context, returns cookies in an array.
# This subroutine works only for portals working with user and password
#@param user uid
#@param password password
#@return session => { error => code , cookies => { cookieName1 => value ,... } }
sub getCookies {
my $self = shift;
$self->{error} = PE_OK;
( $self->{user}, $self->{password} ) = ( shift, shift );
unless ( $self->{user} && $self->{password} ) {
$self->{error} = PE_FORMEMPTY;
}
else {
$self->{error} = $self->_subProcess(
qw(authInit userDBInit getUser setAuthSessionInfo setSessionInfo
setMacros setGroups authenticate store buildCookie)
);
}
my @tmp = ();
push @tmp, SOAP::Data->name( error => $self->{error} );
my @cookies = ();
unless ( $self->{error} ) {
foreach ( @{ $self->{cookie} } ) {
push @cookies, SOAP::Data->name( $_->name, $_->value );
}
}
else {
my @cookieNames = split /\s+/, $self->{cookieName};
foreach (@cookieNames) {
push @cookies, SOAP::Data->name( $_, 0 );
}
}
push @tmp, SOAP::Data->name( cookies => \SOAP::Data->value(@cookies) );
my $res = SOAP::Data->name( session => \SOAP::Data->value(@tmp) );
$self->updateStatus;
return $res;
}
###############################################################
# MAIN subroutine: call all steps until one returns something #
# different than PE_OK #
@ -585,11 +534,6 @@ sub controlExistingSession {
my %cookies;
%cookies = fetch CGI::Cookie unless ($id);
# Store IP address and start time
$self->{sessionInfo}->{ipAddr} = $ENV{REMOTE_ADDR};
$self->{sessionInfo}->{startTime} =
&POSIX::strftime( "%Y%m%d%H%M%S", localtime() );
# Test if Lemonldap::NG cookie is available
if (
$id
@ -667,8 +611,19 @@ sub existingSession {
# 8. setAuthSessionInfo() : must be implemented in Auth* module:
# * store exported datas in $self->{sessionInfo}
# 9. setSessionInfo() : must be implemented in User* module:
# * store exported datas in $self->{sessionInfo}
##@apmethod int setSessionInfo()
# 9) Call setSessionInfo() in User* module and set ipAddr and startTime
#@return Lemonldap::NG::Portal constant
sub setSessionInfo {
my $self = shift;
# Store IP address and start time
$self->{sessionInfo}->{ipAddr} = $ENV{REMOTE_ADDR};
$self->{sessionInfo}->{startTime} =
&POSIX::strftime( "%Y%m%d%H%M%S", localtime() );
return $self->SUPER::setSessionInfo();
}
##@apmethod int setMacro()
# 10) macro mechanism.
@ -718,7 +673,7 @@ sub setGroups {
}
##@apmethod int authenticate()
# 12. authenticate() : must be implemented in Auth* module.
# 12. Call authenticate() in Auth* module and call userNotice().
#@return Lemonldap::NG::Portal constant
sub authenticate {
my $self = shift;

@ -0,0 +1,163 @@
## @file
# SOAP methods for Lemonldap::NG portal
## @class
# Add SOAP methods to the Lemonldap::NG portal.
package Lemonldap::NG::Portal::_SOAP;
use strict;
use Lemonldap::NG::Portal::Simple;
require SOAP::Lite;
## @method void startSoapServices()
# Check the URI requested (PATH_INFO environment variable) and launch the
# corresponding SOAP methods using soapTest().
sub startSoapServices {
my $self = shift;
if (
my $tmp = {
'/sessions' => 'getAttributes',
'/adminSessions' => 'setAttributes newSession',
'/config' => 'getConfig'
}->{ $ENV{PATH_INFO} }
)
{
$self->soapTest($tmp);
$self->{soapOnly} = 1;
}
else {
$self->soapTest("getCookies error");
}
$self->abort( 'Bad request', 'Only SOAP requests are accepted here' )
if ( $self->{soapOnly} );
}
####################
# SOAP subroutines #
####################
=begin WSDL
_IN user $string User name
_IN password $string Password
_RETURN $getCookiesResponse Response
=end WSDL
=cut
##@method SOAP::Data getCookies(string user,string password)
# Called in SOAP context, returns cookies in an array.
# This subroutine works only for portals working with user and password
#@param user uid
#@param password password
#@return session => { error => code , cookies => { cookieName1 => value ,... } }
sub getCookies {
my $self = shift;
$self->{error} = PE_OK;
( $self->{user}, $self->{password} ) = ( shift, shift );
$self->lmLog( "SOAP authentication request for $self->{user}", 'debug' );
unless ( $self->{user} && $self->{password} ) {
$self->{error} = PE_FORMEMPTY;
}
else {
$self->{error} = $self->_subProcess(
qw(authInit userDBInit getUser setAuthSessionInfo setSessionInfo
setMacros setGroups authenticate store buildCookie)
);
}
my @tmp = ();
push @tmp, SOAP::Data->name( error => $self->{error} );
my @cookies = ();
unless ( $self->{error} ) {
foreach ( @{ $self->{cookie} } ) {
push @cookies, SOAP::Data->name( $_->name, $_->value );
}
}
else {
my @cookieNames = split /\s+/, $self->{cookieName};
foreach (@cookieNames) {
push @cookies, SOAP::Data->name( $_, 0 );
}
}
push @tmp, SOAP::Data->name( cookies => \SOAP::Data->value(@cookies) );
my $res = SOAP::Data->name( session => \SOAP::Data->value(@tmp) );
$self->updateStatus;
return $res;
}
=begin WSDL
_IN id $string Cookie value
_RETURN $getAttributesResponse Response
=end WSDL
=cut
##@method SOAP::Data getAttributes(string id)
# Return attributes of the session identified by $id.
# @param $id Cookie value
# @return SOAP::Data sequence
sub getAttributes {
my ( $self, $id ) = @_;
die 'id is required' unless ($id);
my $h = $self->getApacheSession( $id, 1 );
my @tmp = ();
unless ($h) {
$self->_sub( 'userNotice',
"SOAP attributes request: session $id not found" );
push @tmp, SOAP::Data->name( error => 1 )->type('int');
}
else {
$self->_sub( 'userInfo',
"SOAP attributes request for " . $h->{ $self->{whatToTrace} } );
push @tmp, SOAP::Data->name( error => 0 )->type('int');
push @tmp,
SOAP::Data->name( attributes =>
_buildSoapHash( $h, split /\s+/, $self->{exportedAttr} ) );
untie(%$h);
}
my $res = SOAP::Data->name( session => \SOAP::Data->value(@tmp) );
return $res;
}
##@method SOAP::Data getConfig()
# Return Lemonldap::NG configuration. Warning, this is not a well formed
# SOAP::Data object so it can be difficult to read by other languages than
# Perl. It's not really a problem since this function is written to be read by
# Lemonldap::NG components and is not designed to be shared.
# @return hashref serialized in SOAP by SOAP::Lite
sub getConfig {
my $self = shift;
my $conf = $self->_getLmConf() or die("No configuration available");
return $conf;
}
##@fn private SOAP::Data _buildSoapHash()
# Serialize a hashref into SOAP::Data. Types are fixed to "string".
# @return SOAP::Data serialized datas
sub _buildSoapHash {
my ( $h, @keys ) = @_;
my @tmp = ();
@keys = keys %$h unless (@keys);
foreach (@keys) {
if ( ref( $h->{$_} ) eq 'HASH' ) {
die;
push @tmp, SOAP::Data->name( $_ => _buildSoapHash( $h->{$_} ) );
}
elsif ( ref( $h->{$_} ) eq 'ARRAY' ) {
die;
push @tmp,
SOAP::Data->name( $_, \SOAP::Data->value( @{ $h->{$_} } ) );
}
else {
push @tmp, SOAP::Data->name( $_, $h->{$_} )->type('string')
if ( defined( $h->{$_} ) );
}
}
return \SOAP::Data->value(@tmp);
}
1;
Loading…
Cancel
Save