diff --git a/lemonldap-ng-portal/MANIFEST b/lemonldap-ng-portal/MANIFEST
index 3698467af..5adc01827 100644
--- a/lemonldap-ng-portal/MANIFEST
+++ b/lemonldap-ng-portal/MANIFEST
@@ -62,6 +62,7 @@ lib/Lemonldap/NG/Portal/IssuerDBNull.pm
lib/Lemonldap/NG/Portal/IssuerDBOpenID.pm
lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm
lib/Lemonldap/NG/Portal/IssuerDBSAML.pm
+lib/Lemonldap/NG/Portal/Lib/CAS.pm
lib/Lemonldap/NG/Portal/Lib/Choice.pm
lib/Lemonldap/NG/Portal/Lib/DBI.pm
lib/Lemonldap/NG/Portal/Lib/LDAP.pm
diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/CAS.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/CAS.pm
new file mode 100644
index 000000000..acfd112bb
--- /dev/null
+++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/CAS.pm
@@ -0,0 +1,375 @@
+package Lemonldap::NG::Portal::Lib::CAS;
+
+use strict;
+use Mouse;
+
+use Lemonldap::NG::Portal::Main::Constants qw(
+ PE_OK
+ PE_SENDRESPONSE
+);
+
+our $VERSION = '2.0.0';
+
+# PROPERTIES
+
+# return LWP::UserAgent object
+has ua => (
+ is => 'rw',
+ lazy => 1,
+ builder => sub {
+
+ # TODO : LWP options to use a proxy for example
+ my $ua = LWP::UserAgent->new();
+ push @{ $ua->requests_redirectable }, 'POST';
+ $ua->env_proxy();
+ return $ua;
+ }
+);
+
+# INITIALIZATION
+
+sub init {
+ my ($self) = @_;
+}
+
+sub sendSoapResponse {
+ my ( $self, $req, $s ) = @_;
+ $req->response(
+ [
+ 200,
+ [
+ 'Content-Length' => length($s)
+ ],
+ [$s]
+ ]
+ );
+ return PE_SENDRESPONSE;
+}
+
+# Try to recover the CAS session corresponding to id and return session datas
+# If id is set to undef, return a new session
+sub getCasSession {
+ my ( $self, $id ) = @_;
+
+ my $casSession = Lemonldap::NG::Common::Session->new(
+ {
+ storageModule => $self->conf->{casStorage},
+ storageModuleOptions => $self->conf->{casStorageOptions},
+ cacheModule => $self->conf->{localSessionStorage},
+ cacheModuleOptions => $self->conf->{localSessionStorageOptions},
+ id => $id,
+ kind => "CAS",
+ }
+ );
+
+ if ( $casSession->error ) {
+ if ($id) {
+ $self->p->userInfo("CAS session $id isn't yet available");
+ }
+ else {
+ $self->lmLog( "Unable to create new CAS session", 'error' );
+ $self->lmLog( $casSession->error, 'error' );
+ }
+ return undef;
+ }
+
+ return $casSession;
+}
+
+# Return an error for CAS VALIDATE request
+sub returnCasValidateError {
+ my ( $self, $req ) = @_;
+
+ $self->lmLog( "Return CAS validate error", 'debug' );
+
+ $req->response( [ 200, [ 'Content-Length' => 4 ], ["no\n\n"] ] );
+ return PE_SENDRESPONSE;
+}
+
+# Return success for CAS VALIDATE request
+sub returnCasValidateSuccess {
+ my ( $self, $req, $username ) = @_;
+
+ $self->lmLog( "Return CAS validate success with username $username",
+ 'debug' );
+
+ return $self->sendSoapResponse( $req, "yes\n$username\n" );
+}
+
+# Return an error for CAS SERVICE VALIDATE request
+sub returnCasServiceValidateError {
+ my ( $self, $req, $code, $text ) = @_;
+
+ $code ||= 'INTERNAL_ERROR';
+ $text ||= 'No description provided';
+
+ $self->lmLog( "Return CAS service validate error $code ($text)", 'debug' );
+
+ return $self->sendSoapResponse(
+ $req, "
+\t
+\t\t$text
+\t
+\n"
+ );
+}
+
+# Return success for CAS SERVICE VALIDATE request
+sub returnCasServiceValidateSuccess {
+ my ( $self, $req, $username, $pgtIou, $proxies, $attributes ) = @_;
+
+ $self->lmLog( "Return CAS service validate success with username $username",
+ 'debug' );
+
+ my $s = "$pgtIou\n";
+ }
+ if ($proxies) {
+ $self->lmLog( "Add proxies $proxies in response", 'debug' );
+ $s .= "\t\t\n\t\t\t$_\n"
+ foreach ( split( /$self->{multiValuesSeparator}/, $proxies ) );
+ $s .= "\t\t\n";
+ }
+ $s .= "\t\n\n";
+
+ return $self->sendSoapResponse( $req, $s );
+}
+
+# Return an error for CAS PROXY request
+sub returnCasProxyError {
+ my ( $self, $req, $code, $text ) = @_;
+
+ $code ||= 'INTERNAL_ERROR';
+ $text ||= 'No description provided';
+
+ $self->lmLog( "Return CAS proxy error $code ($text)", 'debug' );
+
+ return $self->sendSoapResponse(
+ $req, "
+\t
+\t\t$text
+\t
+\n"
+ );
+}
+
+# Return success for CAS PROXY request
+sub returnCasProxySuccess {
+ my ( $self, $req, $ticket ) = @_;
+
+ $self->lmLog( "Return CAS proxy success with ticket $ticket", 'debug' );
+
+ return $self->sendSoapResponse(
+ $req, "
+\t
+\t\t$ticket
+\t
+\n"
+ );
+}
+
+# Find and delete CAS sessions bounded to a primary session
+sub deleteCasSecondarySessions {
+ my ( $self, $session_id ) = @_;
+ my $result = 1;
+
+ # Find CAS sessions
+ my $moduleOptions = $self->conf->{casStorageOptions} || {};
+ $moduleOptions->{backend} = $self->conf->{casStorage};
+ my $module = "Lemonldap::NG::Common::Apache::Session";
+
+ my $cas_sessions =
+ $module->searchOn( $moduleOptions, "_cas_id", $session_id );
+
+ if ( my @cas_sessions_keys = keys %$cas_sessions ) {
+
+ foreach my $cas_session (@cas_sessions_keys) {
+
+ # Get session
+ $self->lmLog( "Retrieve CAS session $cas_session", 'debug' );
+
+ my $casSession = $self->getCasSession($cas_session);
+
+ # Delete session
+ $result = $self->deleteCasSession($casSession);
+ }
+ }
+ else {
+ $self->lmLog( "No CAS session found for session $session_id ",
+ 'debug' );
+ }
+
+ return $result;
+
+}
+
+# Delete an opened CAS session
+sub deleteCasSession {
+ my ( $self, $session ) = @_;
+
+ # Check session object
+ unless ( $session && $session->data ) {
+ $self->lmLog( "No session to delete", 'error' );
+ return 0;
+ }
+
+ # Get session_id
+ my $session_id = $session->id;
+
+ # Delete session
+ unless ( $session->remove ) {
+ $self->lmLog( $session->error, 'error' );
+ return 0;
+ }
+
+ $self->lmLog( "CAS session $session_id deleted", 'debug' );
+
+ return 1;
+}
+
+# Call proxy granting URL on CAS client
+sub callPgtUrl {
+ my ( $self, $pgtUrl, $pgtIou, $pgtId ) = @_;
+
+ # Build URL
+ my $url =
+ $pgtUrl . ( $pgtUrl =~ /\?/ ? '&' : '?' ) . "pgtIou=$pgtIou&pgtId=$pgtId";
+
+ $self->lmLog( "Call URL $url", 'debug' );
+
+ # GET URL
+ my $response = $self->ua->get($url);
+
+ # Return result
+ return $response->is_success();
+}
+
+1;
+__END__
+
+=head1 NAME
+
+=encoding utf8
+
+Lemonldap::NG::Portal::_CAS - Common CAS functions
+
+=head1 SYNOPSIS
+
+use Lemonldap::NG::Portal::_CAS;
+
+=head1 DESCRIPTION
+
+This module contains common methods for CAS
+
+=head1 METHODS
+
+=head2 getCasSession
+
+Try to recover the CAS session corresponding to id and return session datas
+If id is set to undef, return a new session
+
+=head2 returnCasValidateError
+
+Return an error for CAS VALIDATE request
+
+=head2 returnCasValidateSuccess
+
+Return success for CAS VALIDATE request
+
+=head2 deleteCasSecondarySessions
+
+Find and delete CAS sessions bounded to a primary session
+
+=head2 returnCasServiceValidateError
+
+Return an error for CAS SERVICE VALIDATE request
+
+=head2 returnCasServiceValidateSuccess
+
+Return success for CAS SERVICE VALIDATE request
+
+=head2 returnCasProxyError
+
+Return an error for CAS PROXY request
+
+=head2 returnCasProxySuccess
+
+Return success for CAS PROXY request
+
+=head2 deleteCasSession
+
+Delete an opened CAS session
+
+=head2 callPgtUrl
+
+Call proxy granting URL on CAS client
+
+=head1 SEE ALSO
+
+L
+
+=head1 AUTHOR
+
+=over
+
+=item Clement Oudot, Eclem.oudot@gmail.comE
+
+=item Xavier Guimard, Ex.guimard@free.frE
+
+=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
+
+=over
+
+=item Copyright (C) 2010-2012 by Clement Oudot, Eclem.oudot@gmail.comE
+
+=item Copyright (C) 2016 by Xavier Guimard, Ex.guimard@free.frE
+
+=back
+
+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