# Base package for simple issuers plugins # # Issuer should just implement a run() method that will be called only for # authenticated users when PATH_INFO starts with issuerDBXXPath # # run() should just return a Lemonldap::NG::Portal::Main::Constants value. It # is called using process() method (Lemonldap::NG::Portal::Main::Process) package Lemonldap::NG::Portal::Main::Issuer; use strict; use Mouse; use MIME::Base64; use IO::String; use Lemonldap::NG::Portal::Main::Constants qw( PE_OK PE_RENEWSESSION ); extends 'Lemonldap::NG::Portal::Main::Plugin'; our $VERSION = '2.0.0'; # PROPERTIES has type => ( is => 'rw' ); has path => ( is => 'rw' ); has _ott => ( is => 'rw', lazy => 1, default => sub { my $ott = $_[0]->{p}->loadModule('::Lib::OneTimeToken'); $ott->timeout( $_[0]->{conf}->{formTimeout} ); return $ott; } ); # INTERFACE # Only logout is called in normal use. Issuer that inherits from this # package are called only by their path sub beforeLogout { 'logout' } # INITIALIZATION sub init { my ($self) = @_; my $type = ref( $_[0] ); $type =~ s/.*:://; $self->type($type); if ( my $path = $self->conf->{"issuerDB${type}Path"} ) { $path =~ s/^.*?(\w+).*?$/$1/; $self->path($path); $self->addUnauthRoute( $path => { '*' => '_redirect' }, [ 'GET', 'POST' ] ); $self->addAuthRoute( $path => { '*' => "_forAuthUser" }, [ 'GET', 'POST' ] ); } else { $self->logger->debug("No path declared for issuer $type. Skipping"); } } # RUNNING METHODS # Case 1: Unauthentified users are redirected to the main portal sub _redirect { my ( $self, $req, @path ) = @_; $self->logger->debug('Processing _redirect'); $self->logger->debug('Store issuer request'); my $ir = $req->param( 'issuerRequest' . $self->path ) || $self->storeRequest($req); $self->p->setHiddenFormValue( $req, 'issuerRequest' . $self->path, $ir, '' ); $req->{urldc} = $self->conf->{portal}; $req->{urldc} =~ s#/*$##; $req->{urldc} .= $req->path . "?issuerRequest$self->{path}=$ir"; $self->p->setHiddenFormValue( $req, 'issuerUrldc', $req->urldc, '', 0 ); if ( my $t = $req->param( 'issuerRequest' . $self->path ) ) { $ir = $t; } # TODO: launch normal process with 'run' at the end return $self->p->do( $req, [ 'controlUrl', @{ $self->p->beforeAuth }, $self->p->authProcess, @{ $self->p->betweenAuthAndDatas }, $self->p->sessionDatas, @{ $self->p->afterDatas }, sub { # Restore urldc if auth doesn't need to dial with browser $self->restoreRequest( $req, $ir ); return $self->run( @_, @path ); } ] ); } # Case 3: authentified user, launch sub _forAuthUser { my ( $self, $req, @path ) = @_; $self->logger->debug('Processing _forAuthUser'); if ( my $r = $req->param( 'issuerRequest' . $self->path ) ) { $self->restoreRequest( $req, $r ); } $req->urlNotBase64(1) if ( ref($self) =~ /::CAS$/ ); return $self->p->do( $req, [ 'importHandlerDatas', 'controlUrl', @{ $self->p->forAuthUser }, sub { return $self->run( @_, @path ); }, ] ); } sub storeRequest { my ( $self, $req ) = @_; my $info = {}; $info->{content} = $req->content; foreach ( keys %{ $req->env } ) { $info->{$_} = $req->env->{$_} unless ( ref $req->env->{$_} ); } return $self->_ott->createToken($info); } sub restoreRequest { my ( $self, $req, $token ) = @_; my $env = $self->_ott->getToken($token); if ($env) { $self->logger->debug("Restoring request from $token"); if ( my $c = delete $env->{content} ) { $env->{'psgix.input.buffered'} = 0; $env->{'psgi.input'} = IO::String->new($c); } $req->{env} = {}; foreach ( keys %$env ) { $self->logger->debug("Restore $_"); $req->env->{$_} = $env->{$_} unless /^plack/; } } return $req; } sub reAuth { my ( $self, $req ) = @_; $req->datas->{_url} = encode_base64( $self->conf->{portal} . $req->path_info . '?issuerRequest' . $self->path . '=' . $self->storeRequest($req), '' ); return PE_RENEWSESSION; } 1; __END__ =pod =encoding utf8 =head1 NAME Lemonldap::NG::Portal::Main::Issuer - Base class for identity providers. =head1 SYNOPSIS package Lemonldap::NG::Portal::Issuer::My; use strict; use Mouse; extends 'Lemonldap::NG::Portal::Main::Issuer'; use Lemonldap::NG::Portal::Main::Constants qw(PE_OK); # Optional initialization method sub init { my ($self) = @_; ... # Must return 1 (succeed) or 0 (failure) } # Required methods are run() and logout(), they are launched only for # authenticated users # $req is a Lemonldap::NG::Portal::Main::Request object # They must return a Lemonldap::NG::Portal::Main::Constants constant sub run { my ( $self, $req ) = @_ ... return PE_OK } sub logout { my ( $self, $req ) = @_ ... return PE_OK } 1; =head1 DESCRIPTION Lemonldap::NG::Portal::Main::Issuer is a base class to write identity providers for Lemonldap::NG web-SSO system. It provide several methods to write easily an IdP and manage authentication if the identity request comes before authentication. =head1 WRITING AN IDENTITY PROVIDER To write a classic identity provider, you just have to inherit this class and write run() and logout() methods. These methods must return a Lemonldap::NG::Portal::Main::Constants constant. A classic identity provider needs a "issuerDBEXXXEPath" parameter in LLNG configuration to declare its base URI path (see L). Example: /saml/. All requests that starts with /saml/ will call run() after authentication if needed, and no one else. The logout() function is called when user asks for logout on this server. If you want to write an identity provider, you must implement a single logout system. =head2 managing other URI path Lemonldap::NG::Portal::Main::Issuer provides methods to bind a method to an URI path: =over =item addAuthRoute() for authenticated users =item addUnauthRoute() for unauthenticated users =back They must be called during initialization process (so you must write the optional init() sub). Example: sub init { my ($self) = @_; ... $self->addUnauthRoute( saml => { soap => 'soapServer' }, [ 'POST' ] ); return 1; } sub soapServer { my ( $self, $req ) = @_; ... # You must return a valid PSGI response return [ 200, [ 'Content-Type' => 'application/xml' ], [] ]; } =head1 SEE ALSO L =head1 AUTHORS =over =item LemonLDAP::NG team L =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =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. =cut