AuthLA.pm 47.7 KB
Newer Older
1 2

#===============================================================================
3
# Liberty Alliance Authentication for LemonLDAP.
4
#-------------------------------------------------------------------------------
5 6
#
# This file is part of the LemonLDAP project and released under GPL.
7 8 9 10 11 12 13 14 15
#
#-------------------------------------------------------------------------------
# CHANGELOGS
#-------------------------------------------------------------------------------
# 2008-03-25 - Version 0.3
# Author(s) : Thomas CHEMINEAU
#   - Fixe some bugs into logout process from IDP or SP ;
#   - Add some checks into general algorithm ;
#
16
#===============================================================================
17 18 19 20 21 22 23

package Lemonldap::NG::Portal::AuthLA;

use strict;

use Lemonldap::NG::Portal::SharedConf qw(:all);
use lasso;
24
use CGI::Cookie;
25
use CGI::Session;
26 27 28 29 30 31
use HTTP::Request;
use HTTP::Response;
use LWP::UserAgent;
use MIME::Base64;
use XML::Simple;
use UNIVERSAL qw( isa can VERSION );
32 33 34 35 36

*EXPORT_OK   = *Lemonldap::NG::Portal::SharedConf::EXPORT_OK;
*EXPORT_TAGS = *Lemonldap::NG::Portal::SharedConf::EXPORT_TAGS;
*EXPORT      = *Lemonldap::NG::Portal::SharedConf::EXPORT;

37
our $VERSION = '0.32';
38
use base qw(Lemonldap::NG::Portal::SharedConf);
39

40 41 42
#===============================================================================
# Global Constants
#===============================================================================
43

44
use constant {
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    PE_LA_FAILED        => 11,
    PE_LA_ARTFAILED     => 12,
    PE_LA_DEFEDFAILED   => 13,
    PE_LA_QUERYEMPTY    => 14,
    PE_LA_SOAPFAILED    => 15,
    PE_LA_SLOFAILED     => 16,
    PE_LA_SSOFAILED     => 17,
    PE_LA_SSOINITFAILED => 18,
    PE_LA_SESSIONERROR  => 19,
    PE_LA_SEPFAILED     => 20,

    PC_LA_URLAC  => '/liberty/assertionConsumer.pl',
    PC_LA_URLFT  => '/liberty/federationTermination.pl',
    PC_LA_URLFTR => '/liberty/federationTerminationReturn.pl',
    PC_LA_URLSL  => '/liberty/singleLogout.pl',
    PC_LA_URLSLR => '/liberty/singleLogoutReturn.pl',
    PC_LA_URLSC  => '/liberty/soapCall.pl',
    PC_LA_URLSE  => '/liberty/soapEndpoint.pl',
63
};
64

65 66 67 68 69 70 71 72 73 74 75 76 77 78
#===============================================================================
#===============================================================================
#
# TODO
# ------------------------------------------------------------------------------
# - category / function : comments
# ------------------------------------------------------------------------------
# - association / store : Replace files by hastable or DBI implementation
# - security / process : Check if URL figures in locationRules
# - security / libertyFederationTermination : Implementation
# - wsf / setSessionInfo : Code for getting informations via wsf protocol
#
#===============================================================================
#===============================================================================
79

80 81 82 83 84 85 86
################################################################################
################################################################################
##                                                                            ##
##                      Lemonldap::NG::Portal functions                       ##
##                                                                            ##
################################################################################
################################################################################
87

88 89 90 91 92 93 94 95 96
#===============================================================================
# new
#===============================================================================
#
# Instanciate this class. This constructor takes special parameters with
# classical Lemonldap::NG::SharedConf parameters.
#
#===============================================================================

97 98 99
sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);
100

101 102
    $self->{isLibertyProcess} = 1;
    $self->{laDebug} = 0 unless ( $self->{laDebug} );
103 104

    die('No Liberty Alliance Service Provider data defined')
105
      unless ( $self->{laSp} );
106
    die('No Liberty Alliance Identity Provider file defined')
107
      unless ( $self->{laIdpsFile} );
108
    die('No laStorage configuration defined')
109
      unless ( $self->{laStorage} );
110
    die('No laLdapLoginAttribute configuration defined')
111
      unless ( $self->{laLdapLoginAttribute} );
112
    die('No localStorage configuration defined')
113
      unless ( $self->{localStorage} and $self->{localStorageOptions} );
114

115 116
    bless( $self, $class );

117
    # Create LassoServer
Clément OUDOT's avatar
Clément OUDOT committed
118

119 120 121 122 123
    $self->{laServer} = lasso::Server->new(
        $self->{laSp}->{metadata},
        $self->{laSp}->{privkey},
        undef,    #$self->{laSp}->{secretkey} ,
        undef,    #$self->{laSp}->{certificate} ,
124
    );
Clément OUDOT's avatar
Clément OUDOT committed
125

126
    $self->_loadXmlIdpFile();
127
    return $self;
128 129
}

130 131 132 133 134 135 136 137 138
#===============================================================================
# authenticate
#===============================================================================
#
# User is authenticated automatically, no ldap authentication.
#
#===============================================================================

sub authenticate {
139 140 141
    my $self = shift;
    return $self->SUPER::authenticate()
      unless ( $self->{isLibertyProcess} );
142
    return PE_BADCREDENTIALS
143
      unless ( defined $self->{user} );
144 145 146 147 148 149 150 151
    return PE_OK;
}

#===============================================================================
# extractFormInfo
#===============================================================================
#
# This function is just override to do nothing.
152
# $self->{user} is already fixed in libertySetSessionInfo function.
153 154 155 156
#
#===============================================================================

sub extractFormInfo {
157 158 159
    my $self = shift;
    return $self->SUPER::extractFormInfo()
      unless ( $self->{isLibertyProcess} );
160 161 162 163 164 165 166 167
    return PE_OK;
}

#===============================================================================
# formateFilter
#===============================================================================
#
# By default, the user is searched in the LDAP server with its UID. Here,
168
# $self->{user} contains nameIdentifier of the user, which is already stored
169 170 171 172
# in LDAP directory.
#
#===============================================================================

173
sub formateFilter {
174 175 176 177 178
    my $self = shift;
    return $self->SUPER::formateFilter()
      unless ( $self->{isLibertyProcess} );
    $self->{filter} =
      "(&(uid=" . $self->{user} . ")(objectClass=inetOrgPerson))";
179 180 181 182 183 184 185 186 187 188 189 190 191
    return PE_OK;
}

#===============================================================================
# process
#===============================================================================
#
# Do portal Lemonldap::NG processing. Actions based on Lemonldap::NG structure
# and philosophy.
#
#===============================================================================

sub process {
192 193
    my $self = shift;
    $self->{error} = PE_OK;
194 195

    # Trace param()
196
    # my @params = $self->param() ;
197
    # foreach( @params ) {
198
    #   $self->_debug("parameter : $_ = " . $self->param($_)) ;
199 200
    # }
    # while(my($k,$v) = each(%ENV)) {
201
    #        $self->_debug("env : $k = $v") ;
202 203 204 205 206 207 208
    # }

    #--------
    # Nothing to do if user access to portal directly. We have to verify if
    # user was redirected from a protected host.
    #--------

209
    my $url = $self->url();
210 211
    my $urlr = $url . substr( $ENV{'SCRIPT_NAME'}, 1 );

212 213
    if ( not $self->param('url')
        and ( $url eq $self->{portal} or $urlr eq $self->{portal} ) )
214
    {
215

216 217
        # TODO Security tricks :
        # - Check if URL figures in locationRules
218
        $self->{error} = PE_DONE;
219
        $self->updateStatus;
220
        return $self->{error};
221 222 223 224 225 226
    }

    #--------
    # Authentication process
    #--------

227
    my $urldir = $self->url( -absolute => 1 );
228 229

    # assertionCustomer
230
    if ( $urldir eq $self->PC_LA_URLAC ) {
231

232
        $self->{error} = $self->_subProcess(
233 234
            qw( libertyAssertionConsumer libertySetSessionInfo ));

235
        $self->_debug( "Login user = '" . $self->{user} . "'" );
236

237
        # federationTermination
238
    }
239
    elsif ( $urldir eq $self->PC_LA_URLFT ) {
240

241
        $self->{error} = $self->_subProcess(
242
            qw( libertyFederationTermination log autoRedirect ));
243

244
        # federationTerminationReturn
245
    }
246
    elsif ( $urldir eq $self->PC_LA_URLFTR ) {
247

248
        $self->{error} = $self->_subProcess(
249 250 251 252
            qw( libertyFederationTerminationReturn log
              autoRedirect )
        );

253
        # singleLogout : called when IDP request Logout.
254
    }
255
    elsif ( $urldir eq $self->PC_LA_URLSL ) {
256

257
        $self->{error} = $self->_subProcess(
258 259 260 261
            qw( libertyRetrieveExistingSession libertySingleLogout
              libertyDeletingExistingSession )
        );

262
        # OK : $self->{urldc} is fixed at the end of this process.
263
        $self->_debug( "Logout user = '" . $self->{user} . "'" );
264

265
        # singleLogoutReturn
266
    }
267
    elsif ( $urldir eq $self->PC_LA_URLSLR ) {
268

269
        $self->{error} =
270
          $self->_subProcess(qw( libertySingleLogoutReturn log ));
271

272
        # soapCall
273
    }
274
    elsif ( $urldir eq $self->PC_LA_URLSC ) {
275

276
        $self->{error} = $self->_subProcess(qw( libertySoapCall log ));
277

278
        # soapEndpoint
279
    }
280
    elsif ( $urldir eq $self->PC_LA_URLSE ) {
281

282
        $self->{error} = $self->_subProcess(qw( libertySoapEndpoint log ));
283

284 285
        # Direct access or simple access -> main
        # WARNING : we permit authentication on service.
286
    }
287 288 289 290
    elsif ( not $self->param('user')
        and not $self->param('password')
        and not $self->param('logout') )
    {
291

292
        $self->{error} = $self->_subProcess(
293 294 295 296 297
            qw( libertyRetrieveExistingSession
              libertyExtractFormInfo libertySignOn log
              autoRedirect )
        );

298
        # Not in liberty authentication process.
299 300
    }
    else {
301
        $self->{isLibertyProcess} = 0;
302 303
    }

304 305
    if ( $self->{error} ) {
        $self->updateStatus;
306
        return 0;
307
    }
308 309

    # Liberty Process OK -> do Lemonldap::NG process.
310 311 312 313 314 315 316 317 318
    # TODO Warning, PE_OK==0 and process returns 0 if an error occurs!
    # my $err = $self->SUPER::process(@_);
    #return $err unless( $err != PE_OK );
    # TODO: Why ? log and  autoRedirect are executed with SUPER::process
    #$err = $self->_subProcess(qw( log autoRedirect ))
    #  if ( $self->{urldc} );
    #return $err;
    # So I think we have just to write this
    return $self->SUPER::process(@_);
319 320
}

321 322 323 324
#===============================================================================
# setSessionInfo
#===============================================================================
#
325 326 327
# After a valid auth assertion consumption, this function is called to init
# session info. If ID-WSF is enabled, get attributes from WebServices, else
# use the standard setSessionInfo (attributes read from LDAP).
328
#
329
# TODO: implement ID-WSF support
330 331
#
#===============================================================================
332

333
sub setSessionInfo {
334
    my $self = shift;
335

336 337
    # If ID-WSF enabled, use WebService
    # TODO
338

339
    # Else use SUPER::setSessionInfo
340
    return $self->SUPER::setSessionInfo;
341
}
342

343 344 345 346 347 348 349 350
#===============================================================================
# store
#===============================================================================
#
# This function store existing association between userNameIdentifier from IDP
# and Apache session ID of Lemonldap::NG.
#
#===============================================================================
351

352
sub store {
353
    my $self = shift;
354

355
    my $err = $self->SUPER::store();
356

357 358
    return $err
      if ( $err != PE_OK or not $self->{isLibertyProcess} );
359

360
    return PE_LA_SESSIONERROR
361
      unless defined $self->{userNameIdentifier};
362

363
    return $self->_assertionSessionStore( $self->{userNameIdentifier} );
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
}

#===============================================================================
#===============================================================================

################################################################################
################################################################################
##                                                                            ##
##                       Some Data Access functions                           ##
##                                                                            ##
################################################################################
################################################################################

#===============================================================================
# getIdpURLs
#===============================================================================
#
# Returns all IDP URLs
#
#===============================================================================

sub getIdpIDs {
386
    my $self = shift;
387 388
    my @tab  = ();

389 390
    if ( $self->{laIdps} ) {
        push @tab, $_ foreach ( keys %{ $self->{laIdps} } );
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
    }

    return @tab;
}

#===============================================================================
#===============================================================================

################################################################################
################################################################################
##                                                                            ##
##                       Liberty Alliance functions                           ##
##                                                                            ##
################################################################################
################################################################################

#===============================================================================
# libertyArtefactResolution
#===============================================================================
#
# This function do Liberty artefact resolution. Verification is already made
# if this function is called, normaly it is authorized.
#
#===============================================================================

sub libertyArtefactResolution {
417
    my $self = shift;
418 419 420 421 422 423 424 425 426

    my $lassoLogin = undef;
    my $lassoHttpMethod =
      ( defined( $ENV{'REQUEST_METHOD'} ) and $ENV{'REQUEST_METHOD'} eq 'GET' )
      ? $lasso::HTTP_METHOD_REDIRECT
      : $lasso::HTTP_METHOD_POST;

    # Retrieve or create lassoLogin.

427 428
    if ( $self->{laLogin} and defined( $self->{laLogin} ) ) {
        $lassoLogin = $self->{laLogin};
429 430
    }
    else {
431
        $lassoLogin = lasso::Login->new( $self->{laServer} );
432 433 434 435 436
    }

    # POST

    if (    $lassoHttpMethod == $lasso::HTTP_METHOD_POST
437
        and $self->param('LARES') )
438 439
    {

440
        my $formLares = $self->param('LARES');
441 442

        if ( my $error = $lassoLogin->processAuthnResponseMsg($formLares) ) {
443
            $self->_debug("lassoLogin->initRequest(...) : error = $error");
444 445 446 447
            return PE_LA_ARTFAILED;
        }

        if ( my $error = $lassoLogin->acceptSso() ) {
448
            $self->_debug("lassoLogin->acceptSso(...) : error = $error");
449 450 451 452 453 454 455 456 457 458 459 460 461
            return PE_LA_SSOFAILED;
        }

        # GET : artefact is in QUERY_STRING param

    }
    elsif ( $lassoHttpMethod == $lasso::HTTP_METHOD_REDIRECT
        and defined $ENV{'QUERY_STRING'} )
    {

        # NOTES :
        #   Documentation indicates that $formLareq is QUERY_STRING HTTP
        #   header. We should have
462
        #     $formLareq = $self->param('QUERY_STRING').
463 464 465 466 467 468
        #   But initRequest method on lassoLogin returns -502 error code
        #   (LASSO_PARAM_ERROR_INVALID_VALUE) when QUERY_STRING is like
        #   'SAMLart=...&RelayState=...'. So, $formLareq is rebuild so
        #   that it only contains 'SAMLart=...'.

        my $formLareq = $ENV{'QUERY_STRING'};
469 470
        if ( $self->param('SAMLart') ) {
            $formLareq = 'SAMLart=' . $self->param('SAMLart');
471 472
        }

473 474 475 476 477 478
        if ( my $error =
            $lassoLogin->initRequest( $formLareq, $lassoHttpMethod ) )
        {
            $self->_debug(
"libertyArtefactResolution : lassoLogin->initRequest(...) : error = $error"
            );
479 480 481 482
            return PE_LA_ARTFAILED;
        }

        if ( my $error = $lassoLogin->buildRequestMsg() ) {
483 484 485
            $self->_debug(
"libertyArtefactResolution : lassoLogin->buildRequestMsg(...) : error = $error"
            );
486 487 488 489 490 491 492
            return PE_LA_ARTFAILED;
        }

        # Check if SSO is OK
        # Successed = $soapResponseMsg contains code 200.

        my $soapResponseMsg =
493
          $self->_soapRequest( $lassoLogin->{msgUrl}, $lassoLogin->{msgBody} );
494 495

        if ( my $error = $lassoLogin->processResponseMsg($soapResponseMsg) ) {
496 497 498
            $self->_debug(
"libertyArtefactResolution : lassoLogin->processResponseMsg(...) : error = $error"
            );
499 500 501 502
            return PE_LA_SOAPFAILED;
        }

        if ( my $error = $lassoLogin->acceptSso() ) {
503 504 505
            $self->_debug(
"libertyArtefactResolution : lassoLogin->acceptSso(...) : error = $error"
            );
506 507 508 509 510 511 512 513 514
            return PE_LA_SSOFAILED;
        }

    }
    else {
        return PE_LA_SSOFAILED;
    }

    # Backup $lassoLogin object
515
    $self->{laLogin} = $lassoLogin;
516 517 518

    # Save RelayState.

519 520
    if ( $self->param('RelayState') ) {
        $self->{urldc} = $self->param('RelayState');
521 522 523 524 525 526 527 528 529 530 531 532 533 534
    }

    return PE_OK;
}

#===============================================================================
# libertyAssertionConsumption
#===============================================================================
#
# Realize assertion.
#
#===============================================================================

sub libertyAssertionConsumer {
535
    my $self = shift;
536

537
    $self->{laLogin} = lasso::Login->new( $self->{laServer} );
538 539

    return PE_LA_SSOFAILED
540 541 542
      unless ( $self->{laLogin}
        and defined( $self->{laLogin} )
        and defined( $self->param('SAMLart') ) );
543

544
    return $self->libertyArtefactResolution(@_);
545 546 547 548 549 550 551 552 553 554 555 556
}

#===============================================================================
# libertyDeletingExistingSession
#===============================================================================
#
# Delete existing Apache session file and Apache session ID <-> nameIdentifier
# association file.
#
#===============================================================================

sub libertyDeletingExistingSession {
557
    my $self = shift;
558 559 560

    # Deleting local cache session shared by all Lemonldap::NG::Handler.

561
    if ( $self->{datas} ) {
562
        my $refLocalStorage     = undef;
563
        my $localStorage        = $self->{localStorage};
564 565 566 567 568 569 570 571 572 573 574
        my $localStorageOptions = {};
        $localStorageOptions->{namespace}          ||= "lemonldap";
        $localStorageOptions->{default_expires_in} ||= 600;

        eval "use $localStorage;";
        die("Unable to load $localStorage: $@") if ($@);

        eval '$refLocalStorage = new '
          . $localStorage
          . '($localStorageOptions);';
        if ( defined $refLocalStorage ) {
575
            $refLocalStorage->remove( ${ $self->{datas} }{_session_id} );
576 577 578
            $refLocalStorage->purge();
        }
        else {
579
            $self->_debug("Deleting apache session failed");
580 581 582 583 584 585
        }
    }

    # Deleting association file, which is created when asserting consumer,
    # in store function.

586
    if ( $self->{sessionInfo}->{'laNameIdentifier'} ) {
587
        return $self->_assertionSessionDelete(
588
            $self->{sessionInfo}->{'laNameIdentifier'} );
589 590 591 592 593 594 595 596 597 598 599 600 601 602
    }

    return PE_OK;
}

#===============================================================================
# libertyExtractFormInfo
#===============================================================================
#
# Verify that user has choose a IDP for authentication.
#
#===============================================================================

sub libertyExtractFormInfo {
603
    my $self = shift;
604 605 606

    # If only one IDP -> redirect automatically on this IDP
    my $idp;
607 608 609
    my @idps = keys %{ $self->{laIdps} };
    if ( $#idps >= 0 && $self->param('idpChoice') ) {
        $idp = $self->param('idpChoice');
610 611 612
    }
    return PE_FIRSTACCESS
      unless $idp;
613
    $self->{idp}->{id} = $idp;
614 615 616 617 618 619 620 621 622
    return PE_OK;
}

#===============================================================================
# libertyFederationTermination
#===============================================================================
#
# Terminate federation.
#
623
# TODO
624 625 626 627
#
#===============================================================================

sub libertyFederationTermination {
628
    my $self = shift;
629

630
    # $self->_debug("Processing federation termination...");
631 632 633 634 635 636

    my $query = $ENV{'QUERY_STRING'};
    return PE_LA_QUERYEMPTY
      unless $query;

    if ( lasso::isLibertyQuery($query) ) {
637 638
        $self->{lassoDefederation} =
          lasso::Defederation->new( $self->{laServer} );
639 640

        return PE_LA_DEFEDFAILED
641 642 643
          unless ( $self->{lassoDefederation}
            and defined( $self->{lassoDefederation} )
            and $self->{lassoDefederation}->processNotificationMsg($query) );
644

645
        # $self->_debug("lassoDefederation->processNotificationMsg... OK");
646 647

        # TODO :
648
        #   $self->fedTerm();
649 650 651 652 653 654 655 656 657 658

        return PE_OK;
    }
    return PE_DONE;
}

#===============================================================================
# libertyFederationTerminationReturn
#===============================================================================
#
659
# TODO
660 661 662 663
#
#===============================================================================

sub libertyFederationTerminationReturn {
664
    my $self = @_;
665

666
    # $self->_debug("The Return of the federation termination...");
667
    $self->{urldc} = $self->{portal};
668
    return PE_OK;
669 670
}

671 672 673 674 675 676 677 678 679
#===============================================================================
# libertyRetrieveExistingSession
#===============================================================================
#
# Try to restore session whithin userNameIdentifier.
#
#===============================================================================

sub libertyRetrieveExistingSession {
680
    my $self = shift;
681

682 683
    # To retrieve current Liberty session, there is one way :
    #   - We have a query string that contains the userNameIdentifier.
684 685 686
    #     Then, we could retrieve apache session from cache files.

    return PE_LA_SESSIONERROR
687
      unless ( defined $self->{laStorageOptions}->{Directory} );
688

689
    return PE_OK
690
      unless ( defined $self->param('NameIdentifier') );
691

692 693
    # Retrieve the Apache session ID.
    # It should not return any errors when trying to retrieve assertions.
694

695 696
    my $err =
      $self->_assertionSessionRetrieve( $self->param('NameIdentifier') );
697

698 699
    # return PE_LA_SESSIONERROR
    #   unless $err == 0 ;
700

701 702 703
    # We can not rebuild factice cookie for Lemonldap::NG retrieving itself the
    # session. So, we retrieve here directly the session. This is the
    # Lemonldap::NG::Simple code of controlExistingSession function.
704

705 706 707
    my %h;
    eval {
        tie %h,
708
          $self->{globalStorage}, $self->{id}, $self->{globalStorageOptions};
709
    };
710

711
    if ( $@ or not tied(%h) ) {
712 713 714 715
        print STDERR "Session "
          . $self->{id}
          . " isn't yet available ($ENV{REMOTE_ADDR})\n";
        return PE_OK;
716
    }
717

718
    %{ $self->{datas} } = %h;
719
    untie(%h);
720

721
    my $r;
722
    if ( $self->{existingSession} ) {
723 724
        $r =
          &{ $self->{existingSession} }( $self, $self->{id}, $self->{datas} );
725 726
    }
    else {
727
        $r = $self->existingSession( $self->{id}, $self->{datas} );
728
    }
729

730 731 732
    if ( $r == PE_OK ) {
        print STDERR "No existing liberty session found\n";
        return PE_OK;
733
    }
734

735 736
    while ( my ( $k, $v ) = each( %{ $self->{datas} } ) ) {
        $self->{sessionInfo}->{$k} = $v;
737
    }
738

739 740
    if ( defined $self->{sessionInfo}->{ $self->{laLdapLoginAttribute} } ) {
        $self->{user} = $self->{sessionInfo}->{ $self->{laLdapLoginAttribute} };
741 742 743
    }

    return PE_OK;
Clément OUDOT's avatar
Clément OUDOT committed
744 745
}

746 747 748 749 750 751 752 753 754 755 756
#===============================================================================
# libertySetSessionInfo
#===============================================================================
#
# This function store in session cache information retrieve from IDP. If
# ID-WSF option is specified, it also store ID-WSF-attributes.
# In all cases, it fixes username of user who is authenticated on IDP.
#
#===============================================================================

sub libertySetSessionInfo {
757
    my $self = shift;
758 759

    return PE_LA_FAILED
760
      unless ( defined $self->{laLogin} );
761

762
    my $lassoLogin = $self->{laLogin};
763 764 765 766 767 768 769

    # Store identity in LDAP Directory, if identity not exists. Good
    # opportunity to ask user some more informations.

    return PE_LA_FAILED
      unless (
        defined $lassoLogin->{session}
770

771 772 773 774 775 776 777 778
        # and defined $lassoLogin->{identity}
        and $lassoLogin->{nameIdentifier}->{content}
      );

    # Here, we store liberty identity and session in Apache session.
    # We just store informations in cache, then those are saved by
    # store function. Saved nameIdentifier too.

779 780 781
    # $self->{sessionInfo}->{laIdentityDump} = $lassoLogin->{identity}->dump() ;
    $self->{sessionInfo}->{laSessionDump} = $lassoLogin->{session}->dump();
    $self->{sessionInfo}->{laNameIdentifier} =
782 783 784 785 786 787 788
      $lassoLogin->{nameIdentifier}->{content};

    # Get username from assertion and restore it in param('user'). Be
    # carefull, IDP does not return username but an id for user assertion.
    # The Lemonldap::NG search consists to perform a search with a filter
    # using nameIdentifier, instead of username.

789 790
    $self->{userNameIdentifier} = $lassoLogin->{nameIdentifier}->{content};
    $self->{password}           = 'none';
791 792 793

    # Try to retrieve uid in SAML response form assertion statement.
    # For the moment, uid have to be unique in LDAP directory.
794

795
    my @uidValues =
796 797
      $self->_getAttributeValuesOfSamlAssertion( $lassoLogin->{response},
        $self->{laLdapLoginAttribute} );
798

799
    $self->{user} = $uidValues[0]
800 801 802 803 804 805 806 807 808
      if (@uidValues);

    return PE_OK;
}

#===============================================================================
# libertySignOn
#===============================================================================
#
809
# Init SSO request. If successfull, $self->{urldc} contains Liberty IDP Url
810 811 812 813 814
# for redirection.
#
#===============================================================================

sub libertySignOn {
815
    my $self = shift;
816

817
    my $lassoLogin = lasso::Login->new( $self->{laServer} );
818 819 820 821 822 823

    return PE_LA_FAILED
      unless ( $lassoLogin and defined($lassoLogin) );

    # TODO :
    #   Catching error when retrieving $providerID.
Clément OUDOT's avatar
Clément OUDOT committed
824

825
    my $providerID = $self->{LAidps}->{ $self->{idp}->{id} }->{url};
Clément OUDOT's avatar
Clément OUDOT committed
826

827 828 829 830 831 832
    if (
        my $error = $lassoLogin->initAuthnRequest(
            $providerID, $lasso::HTTP_METHOD_REDIRECT
        )
      )
    {
833
        $self->_debug("lassoLogin->initAuthnRequest(...) : error = $error");
834 835
        return PE_LA_SSOINITFAILED;
    }
Clément OUDOT's avatar
Clément OUDOT committed
836

837 838 839 840 841 842
    # We do one time federation, IDP doe not have to store nameIdentifier.

    $lassoLogin->{request}->{consent} = $lasso::LIB_CONSENT_OBTAINED;
    $lassoLogin->{request}->{nameIdPolicy} =
      $lasso::LIB_NAMEID_POLICY_TYPE_ONE_TIME;

843
#$lassoLogin->{request}->{nameIdPolicy} = $lasso::LIB_NAMEID_POLICY_TYPE_FEDERATED ;
844 845
    $lassoLogin->{request}->{isPassive} = 0;

846 847
    if ( $self->param('url') ) {
        my $url = decode_base64( $self->param('url') );
848 849 850 851 852
        chomp $url;
        $lassoLogin->{request}->{relayState} = $url;
    }

    if ( my $error = $lassoLogin->buildAuthnRequestMsg() ) {
853
        $self->_debug("lassoLogin->buildAuthnRequestMsg(..) : error = $error");
854 855 856
        return PE_LA_SSOINITFAILED;
    }

857
    $self->{urldc} = $lassoLogin->{msgUrl};
858
    return PE_OK;
Clément OUDOT's avatar
Clément OUDOT committed
859 860
}

861 862 863 864 865
#===============================================================================
# libertySingleLogout
#===============================================================================
#
# Two cases :
866 867 868
#         * Portal or applications requiere singleLogout -> SP request ;
#         * IDP requiere singleLogout -> IDP request with $ENV{'QUERY_STRING'}
#           specified.
869
#
870 871 872
# This function one optional parameter that specifies if the portal is called
# through a SOAP call.
#
873 874 875
#===============================================================================

sub libertySingleLogout {
876 877
    my $self = shift;
    my $soap = shift;
878

879
    my $lassoLogout = lasso::Logout->new( $self->{laServer} );
880 881 882 883 884
    return PE_LA_FAILED
      unless ( $lassoLogout
        and defined($lassoLogout)
        and defined $ENV{'QUERY_STRING'} );

885
    if ( lasso::isLibertyQuery( $ENV{'QUERY_STRING'} ) ) {
886 887 888 889 890

        # We retrieve query string and verify it.
        # If it is OK, we set lemonldap::ng logout parameter, so we can perform
        # it in Lemonldap::NG normal process. Then, we remove our stored liberty
        # association file.
Clément OUDOT's avatar
Clément OUDOT committed
891

892
        $self->param( 'logout' => '1' );
Clément OUDOT's avatar
Clément OUDOT committed
893

894 895 896 897 898
        if ( my $error =
            $lassoLogout->processRequestMsg( $ENV{'QUERY_STRING'} ) )
        {
            $self->_debug(
                "lassoLogout->processRequestMsg(...) : error = $error");
899 900
            return PE_LA_SLOFAILED;
        }
Clément OUDOT's avatar
Clément OUDOT committed
901

902 903
# my $lassoIdentity = lasso::Identity::newFromDump($self->{sessionInfo}->{laIdentityDump}) ;
# $lassoLogout->{identity} = $lassoIdentity ;
904
        my $lassoSession =
905
          lasso::Session::newFromDump( $self->{sessionInfo}->{laSessionDump} );
906 907 908 909 910 911 912
        $lassoLogout->{session} = $lassoSession;

        # Logout by soap call could failed with those two errors.
        if ( my $error = $lassoLogout->validateRequest() ) {
            if (    $error != $lasso::PROFILE_ERROR_SESSION_NOT_FOUND
                and $error != $lasso::PROFILE_ERROR_IDENTITY_NOT_FOUND )
            {
913 914
                $self->_debug(
                    "lassoLogout->validateRequest(...) : error = $error");
915 916 917 918 919
                return PE_LA_SLOFAILED;
            }
        }

        if ( my $error = $lassoLogout->buildResponseMsg() ) {
920 921
            $self->_debug(
                "lassoLogout->buildResponseMsg(...) : error = $error");
922 923 924
            return PE_LA_SLOFAILED;
        }

925 926 927
        # Confirm logout by soap request, only if portal is not already called
        # itself by a SOAP request.
        if ( defined $lassoLogout->{msgBody} && !$soap ) {
928
            my $soapResponseMsg = $self->_soapRequest( $lassoLogout->{msgUrl},
929
                $lassoLogout->{msgBody} );
930 931 932 933
        }
    }

    # Fixes redirection.
934
    $self->{urldc} = $lassoLogout->{msgUrl};
935

936 937 938
    # If $self->{urldc} empty, then we try to use HTTP referer if it exists
    $self->{urldc} = $ENV{'HTTP_REFERER'}
      if ( not $self->{urldc} and $ENV{'HTTP_REFERER'} );
939 940

    return PE_OK;
Clément OUDOT's avatar
Clément OUDOT committed
941 942
}

943 944 945 946 947 948 949 950 951
#===============================================================================
# libertySingleLogoutReturn
#===============================================================================
#
# SP provides singleLogout, it calls IDP which has done Liberty logout. Then IDP
# requests http://portal/liberty/singleLogoutReturn.
# Here, as there is no more liberty session on IDP, we suppress liberty session
# on Lemon.
#
952 953
# TODO: Modify redirect to call handler logout configured in location directive
# (dedicated portal or handler, nor more relaystate need)
954 955 956 957 958
#
#
#===============================================================================

sub libertySingleLogoutReturn {
959
    my $self = shift;
960

961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
# Original code from Unwind :
#
# 8<--------
#    logout = lasso.Logout(misc.get_lasso_server())
#    try:
#        logout.processResponseMsg(get_request().get_query())
#    except lasso.Error, error:
#        if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
#            raise AccessError()
#        if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
#            return error_page(_('Failed to check single logout request signature.'))
#        if hasattr(lasso, 'LOGOUT_ERROR_REQUEST_DENIED') and \
#                error[0] == lasso.LOGOUT_ERROR_REQUEST_DENIED:
#            # ignore silently
#            return redirect(get_request().environ['SCRIPT_NAME'] + '/')
#        elif error[0] == lasso.ERROR_UNDEFINED:
#            # XXX: unknown status; ignoring for now.
#            return redirect(get_request().environ['SCRIPT_NAME'] + '/')
#        raise
#    return redirect(get_request().environ['SCRIPT_NAME'] + '/')
# 8<--------
#
# Normaly, if we are here, assertion and session should have been removed
# in a previous request.

    $self->{lassoLogout} = lasso::Logout->new( $self->{laServer} );
987

988
    return PE_LA_SLOFAILED
989
      unless $self->{lassoLogout} and defined( $self->{lassoLogout} );
990

991 992
    if ( my $error =
        $self->{lassoLogout}->processResponseMsg( $ENV{'QUERY_STRING'} ) )
993
    {
994 995
        $self->_debug("Process response message error = $error");
        return PE_LA_SLOFAILED;
996
    }
997 998 999 1000

    # Test if Lemonldap::NG cookie is available. If it is the case, the
    # corresponding session should be previously deleted.

1001
    return PE_OK;
1002 1003 1004 1005 1006 1007 1008 1009
}

#===============================================================================
# libertySoapCall
#===============================================================================
#
# IDP request defederation by SOAP calls.
#
1010
# TODO
1011