|
|
|
## @file
|
|
|
|
# Module for SAML Common Domain Cookie Support
|
|
|
|
|
|
|
|
## @class Lemonldap::NG::Portal::CDC
|
|
|
|
# Class for SAML Common Domain Cookie Support
|
|
|
|
package Lemonldap::NG::Portal::CDC;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
|
|
|
use MIME::Base64;
|
|
|
|
|
|
|
|
our $VERSION = '2.0.0';
|
|
|
|
|
|
|
|
extends 'Lemonldap::NG::Common::PSGI';
|
|
|
|
|
|
|
|
# PROPERTIES
|
|
|
|
|
|
|
|
has cdc_name => ( is => 'rw' );
|
|
|
|
has cdc_domain => ( is => 'rw' );
|
|
|
|
has httpOnly => ( is => 'rw' );
|
|
|
|
has cookieExpiration => ( is => 'rw' );
|
|
|
|
has oldStyleUrl => ( is => 'rw' );
|
|
|
|
has cdc_values => ( is => 'rw' );
|
|
|
|
|
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
sub init {
|
|
|
|
my ( $self, $args ) = @_;
|
|
|
|
my $tmp = Lemonldap::NG::Common::Conf->new( $args->{configStorage} );
|
|
|
|
unless ($tmp) {
|
|
|
|
$self->error(
|
|
|
|
"Unable to build configuration: $Lemonldap::NG::Common::Conf::msg");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
my $conf = $tmp->getConf();
|
|
|
|
unless ( ref($conf) ) {
|
|
|
|
$self->error(
|
|
|
|
"Unable to load configuration: $Lemonldap::NG::Common::Conf::msg");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
$self->cdc_name( $conf->{samlCommonDomainCookieName} || '_saml_idp' );
|
|
|
|
$self->cdc_domain( $conf->{samlCommonDomainCookieDomain} );
|
|
|
|
$self->lmLog( "[CDC] Cookie name: " . $self->cdc_name, 'debug' );
|
|
|
|
$self->lmLog(
|
|
|
|
"[CDC] Domain name: "
|
|
|
|
. ( $self->cdc_domain ? $self->cdc_domain : '<host name>' ),
|
|
|
|
'debug'
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach (qw(httpOnly cookieExpiration oldStyleUrl)) {
|
|
|
|
$self->$_( $conf->{$_} );
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method int process()
|
|
|
|
# Main method to process CDC requests
|
|
|
|
# @return portal error code
|
|
|
|
sub handler {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
my $cdc_idp = "";
|
|
|
|
my $cdc_cookie = "";
|
|
|
|
|
|
|
|
# Default values
|
|
|
|
my $cdc_domain = $self->cdc_domain || $req->hostname;
|
|
|
|
|
|
|
|
# Request parameter
|
|
|
|
my $action = $req->param('action') || ""; # What we do
|
|
|
|
my $idp = $req->param('idp'); # IDP ID in write mode
|
|
|
|
|
|
|
|
# TODO: Control URL
|
|
|
|
#my $control_url = $self->_sub('controlUrlOrigin');
|
|
|
|
#unless ( $control_url == PE_OK ) {
|
|
|
|
# $self->lmLog( "[CDC] Bad URL", 'error' );
|
|
|
|
# return $control_url;
|
|
|
|
#}
|
|
|
|
|
|
|
|
# Get cookie
|
|
|
|
my %cookies =
|
|
|
|
map { /=/ ? ( split /=/, $_ ) : () } split( /;\s*/, $req->cookie );
|
|
|
|
$cdc_cookie = $cookies{ $self->cdc_name } if %cookies;
|
|
|
|
|
|
|
|
if ($cdc_cookie) {
|
|
|
|
$self->lmLog( "[CDC] Cookie found with value $cdc_cookie", 'debug' );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Write request
|
|
|
|
# Called in an iFrame
|
|
|
|
# Get or build common domain cookie
|
|
|
|
# Append IDP to common domain cookie
|
|
|
|
if ( $action eq 'write' ) {
|
|
|
|
|
|
|
|
$self->lmLog( "[CDC] Write request detected", 'debug' );
|
|
|
|
|
|
|
|
# Check IDP value
|
|
|
|
unless ($idp) {
|
|
|
|
return $self->sendError( $req, "[CDC] No IDP given", 400 );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Add IDP value
|
|
|
|
$self->lmLog( "[CDC] Will add IDP $idp to IDP list", 'debug' );
|
|
|
|
|
|
|
|
my $encoded_idp = encode_base64( $idp, '' );
|
|
|
|
|
|
|
|
# Remove IDP value if already present
|
|
|
|
$cdc_cookie =~ s/$encoded_idp(\s+)?//g;
|
|
|
|
|
|
|
|
# Add a space separator
|
|
|
|
$cdc_cookie .= ( $cdc_cookie ? " " : "" );
|
|
|
|
$cdc_cookie .= $encoded_idp;
|
|
|
|
|
|
|
|
$self->lmLog(
|
|
|
|
"[CDC] Build cookie $self->{cdc_name} with value $cdc_cookie",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
# Build cookie
|
|
|
|
push @{ $req->respHeaders },
|
|
|
|
'Set-Cookie' => $self->cdc_name
|
|
|
|
. "=$cdc_cookie; domain=$cdc_domain; path=/; secure=1; HttpOnly="
|
|
|
|
. $self->httpOnly
|
|
|
|
. "; expires="
|
|
|
|
. $self->cookieExpiration;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Read request
|
|
|
|
# Get last IDP from domain cookie
|
|
|
|
# Return on SP with idp as parameter
|
|
|
|
|
|
|
|
elsif ( $action eq 'read' ) {
|
|
|
|
|
|
|
|
$self->lmLog( "[CDC] Read request detected", 'debug' );
|
|
|
|
|
|
|
|
# Get last IDP from cookie
|
|
|
|
if ($cdc_cookie) {
|
|
|
|
$cdc_idp = decode_base64( ( split /\s+/, $cdc_cookie )[-1] );
|
|
|
|
$self->lmLog( "[CDC] Get value $cdc_idp", 'debug' );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog( "[CDC] No cookie, set a default value", 'debug' );
|
|
|
|
$cdc_idp = 'notfound';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Redirect if needed
|
|
|
|
if ( my $url = $req->param('url') ) {
|
|
|
|
|
|
|
|
# Decode URL
|
|
|
|
if ( $url =~ m#[^A-Za-z0-9\+/=]# ) {
|
|
|
|
return $self->sendError( $req, "Bad URL", 400 );
|
|
|
|
}
|
|
|
|
my $urldc = decode_base64($url);
|
|
|
|
|
|
|
|
# Add CDC IDP in return URL if needed
|
|
|
|
# olStyleUrl can be set to 1 to use & instead of ;
|
|
|
|
$urldc .= (
|
|
|
|
$cdc_idp
|
|
|
|
? (
|
|
|
|
$urldc =~ /\?/
|
|
|
|
? ( $self->{oldStyleUrl} ? '&' : ';' ) . 'idp=' . $cdc_idp
|
|
|
|
: '?idp=' . $cdc_idp
|
|
|
|
)
|
|
|
|
: ''
|
|
|
|
);
|
|
|
|
|
|
|
|
# Redirect
|
|
|
|
return [ 302, [ Location => $urldc, @{ $req->respHeaders } ], [] ];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($cdc_cookie) {
|
|
|
|
|
|
|
|
# Parse cookie to display it if not redirected
|
|
|
|
my @cdc_values =
|
|
|
|
map( decode_base64($_), ( split( /\s+/, $cdc_cookie ) ) );
|
|
|
|
$self->{cdc_values} = \@cdc_values;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [
|
|
|
|
200,
|
|
|
|
[
|
|
|
|
'Content-Type' => 'text/plain',
|
|
|
|
'Content-Length' => 2,
|
|
|
|
@{ $req->respHeaders }
|
|
|
|
],
|
|
|
|
['OK']
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
=encoding utf8
|
|
|
|
|
|
|
|
Lemonldap::NG::Portal::CDC - Manage SAML Common Domain Cookie
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
Choose any L<Plack> method. CGI example:
|
|
|
|
|
|
|
|
#!/usr/bin/env plackup
|
|
|
|
|
|
|
|
use Lemonldap::NG::Portal::CDC;
|
|
|
|
|
|
|
|
# This must be the last instruction ! See PSGI for more
|
|
|
|
Lemonldap::NG::Portal::CDC->run($opts);
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
Lemonldap::NG::Portal::CDC - Manage SAML Common Domain Cookie
|
|
|
|
|
|
|
|
See L<http://lemonldap-ng.org> for more.
|
|
|
|
|
|
|
|
=head1 SEE ALSO
|
|
|
|
|
|
|
|
L<http://lemonldap-ng.org/>
|
|
|
|
|
|
|
|
=head1 AUTHORS
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item LemonLDAP::NG team L<http://lemonldap-ng.org/team>
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=head1 BUG REPORT
|
|
|
|
|
|
|
|
Use OW2 system to report bug or ask for features:
|
|
|
|
L<http://jira.ow2.org>
|
|
|
|
|
|
|
|
=head1 DOWNLOAD
|
|
|
|
|
|
|
|
Lemonldap::NG is available at
|
|
|
|
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>
|
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
|
|
|
|
See COPYING file for details.
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see L<http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
=cut
|