Commit 0ef263b3 authored by Xavier Guimard's avatar Xavier Guimard

OpenID 2 in progress (#595)

parent 0ab55238
......@@ -57,6 +57,7 @@ lib/Lemonldap/NG/Portal/CDC.pm
lib/Lemonldap/NG/Portal/Display.pm
lib/Lemonldap/NG/Portal/Issuer/CAS.pm
lib/Lemonldap/NG/Portal/Issuer/Get.pm
lib/Lemonldap/NG/Portal/Issuer/OpenID.pm
lib/Lemonldap/NG/Portal/Issuer/OpenIDConnect.pm
lib/Lemonldap/NG/Portal/Issuer/SAML.pm
lib/Lemonldap/NG/Portal/IssuerDBCAS.pm
......
package Lemonldap::NG::Portal::Issuer::OpenID;
use strict;
use JSON;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_BADPARTNER
PE_CONFIRM
PE_ERROR
PE_OK
PE_OPENID_BADID
PE_OPENID_EMPTY
PE_REDIRECT
PE_SENDRESPONSE
);
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Issuer';
# INTERFACE
sub beforeAuth { 'forUnauthUser' }
# PROPERTIES
has secret => (
is => 'rw',
default => sub {
return $_[0]->conf->{openIdIssuerSecret}
|| $_[0]->conf->{cipher}->encrypt(0);
}
);
has listIsWhite => (
is => 'rw',
default => sub {
( $_[0]->conf->{openIdSPList} =~ /^(\d);/ )[0] + 0;
}
);
has spList => (
is => 'rw',
default => sub {
Lemonldap::NG::Common::Regexp::reDomainsToHost(
( $_[0]->conf->{openIdSPList} =~ /^\d;(.*)$/ )[0] );
}
);
has openidPortal => (
is => 'rw',
default => sub {
return $_[0]->conf->{portal} . '/' . $_[0]->path;
#$openidPortal =~ s#(?<!:)//#/#g;
}
);
# INITIALIZATION
sub init {
my ($self) = @_;
eval { require Lemonldap::NG::Portal::Lib::OpenID::Server };
if ($@) {
$self->error("Unable to load Net::OpenID::Server: $@");
return 0;
}
return 1;
}
# RUNNING METHOD
sub forUnauthUser {
my ( $self, $req ) = @_;
my $mode = $req->param('openid.mode');
unless ($mode) {
$self->lmLog( 'OpenID SP test', 'debug' );
return PE_OPENID_EMPTY;
}
if ( $mode eq 'associate' ) {
return $self->_openIDResponse(
$self->openIDServer($req)->_mode_associate() );
}
elsif ( $mode eq 'check_authentication' ) {
return $self->_openIDResponse(
$self->openIDServer($req)->_mode_check_authentication() );
}
return PE_OK;
}
sub run {
my ( $self, $req ) = @_;
my $mode = $req->param('openid.mode');
unless ($mode) {
$self->lmLog( 'OpenID SP test', 'debug' );
return PE_OPENID_EMPTY;
}
unless ( $mode =~ /^checkid_(?:immediate|setup)/ ) {
$self->lmLog(
"OpenID error : $mode is not known at this step (issuerForAuthUser)",
'error'
);
return PE_ERROR;
}
my @r = $self->openIDServer($req)->_mode_checkid();
return $self->_openIDResponse(@r);
}
sub logout {
PE_OK;
}
# INTERNAL METHODS
# Create if not done a new Lemonldap::NG::Portal::Lib::OpenID::Server objet
sub openIDServer {
my ( $self, $req ) = @_;
return $req->datas->{_openidserver} if ( $req->datas->{_openidserver} );
$req->datas->{_openidserver} = Lemonldap::NG::Portal::Lib::OpenID::Server->new(
server_secret => sub { return $self->secret },
args => $req,
endpoint_url => $self->openidPortal,
setup_url => $self->openidPortal,
get_user => sub {
return $req->{sessionInfo}
->{ $self->conf->{openIdAttr} || $self->conf->{whatToTrace} };
},
get_identity => sub {
my ( $u, $identity ) = @_;
return $identity unless $u;
return $self->openidPortal . $u;
},
is_identity => sub {
my ( $u, $identity ) = @_;
return 0 unless ( $u and $identity );
if ( $u eq ( split '/', $identity )[-1] ) {
return 1;
}
else {
$self->{_badOpenIdentity} = 1;
return 0;
}
},
is_trusted => sub {
my ( $u, $trust_root, $is_identity ) = @_;
return 0 unless ( $u and $is_identity );
my $tmp = $trust_root;
$tmp =~ s#^http://(.*?)/#$1#;
if ( $tmp =~ $self->spList xor $self->listIsWhite ) {
$self->lmLog( "$trust_root is forbidden for openID exchange",
'warn' );
$req->datas->{_openIdForbidden} = 1;
return 0;
}
elsif ( $req->{sessionInfo}->{"_openidTrust$trust_root"} ) {
$self->lmLog( 'OpenID request already trusted', 'debug' );
return 1;
}
elsif ( $req->param("confirm") == 1 ) {
$self->p->updatePersistentSession(
{ "_openidTrust$trust_root" => 1 } );
return 1;
}
elsif ( $req->param("confirm") == -1 ) {
$self->p->updatePersistentSession(
{ "_openidTrust$trust_root" => 0 } );
return 0;
}
else {
$self->lmLog( 'OpenID request not trusted', 'debug' );
$req->datas->{_openIdTrustRequired} = 1;
return 0;
}
},
extensions => {
sreg => sub {
return ( 1, {} ) unless (@_);
require Lemonldap::NG::Portal::Lib::OpenID::SREG;
return $self->Lemonldap::NG::Portal::Lib::OpenID::SREG::sregHook($req, @_);
},
},
);
return $req->datas->{_openidserver};
}
# Manage Lemonldap::NG::Portal::OpenID::Server responses
# @return Lemonldap::NG::Portal error code
sub _openIDResponse {
my ( $self, $req, $type, $data ) = @_;
# Redirect
if ( $type eq 'redirect' ) {
$self->lmLog( "OpenID redirection to $data", 'debug' );
$req->{urldc} = $data;
return PE_REDIRECT;
}
# Setup
elsif ( $type eq 'setup' ) {
if ( $req->datas->{_openIdTrustRequired}
or $req->datas->{_openIdTrustExtMsg} )
{
# TODO
$req->info('<h3 trspan="openidExchange,$data->{trust_root}"></h3>');
$self->info( $req->datas->{_openIdTrustExtMsg} )
if ( $req->datas->{_openIdTrustExtMsg} );
$self->lmLog( 'OpenID confirmation', 'debug' );
return PE_CONFIRM;
}
elsif ( $req->datas->{_badOpenIdentity} ) {
$self->p->userNotice(
"The user $req->{sessionInfo}->{_user} tries to use the id \"$data->{identity}\" on $data->{trust_root}"
);
return PE_OPENID_BADID;
}
elsif ( $req->datas->{_openIdForbidden} ) {
return PE_BADPARTNER;
}
# User has refused sharing its datas
else {
$self->userNotice(
$req->{sessionInfo}->{ $self->conf->{whatToTrace} }
. ' refused to share its OpenIdentity' );
return PE_OK;
}
}
elsif ($type) {
$self->lmLog( "OpenID generated page ($type)", 'debug' );
$req->response( [ 200, [ 'Content-Type' => $type ], [$data] ] );
}
else {
$req->response(
$self->p->sendError(
$req,
'OpenID error ',
$self->openIDServer($req)->err()
)
);
}
return PE_SENDRESPONSE;
}
1;
......@@ -13,16 +13,16 @@ use Lemonldap::NG::Common::Regexp;
# Hook called to add SREG parameters to the OpenID response
# @return Hash containing wanted parameters
sub sregHook {
my ( $self, $u, $trust_root, $is_id, $is_trusted, $prm ) = @_;
my ( $self, $req, $u, $trust_root, $is_id, $is_trusted, $prm ) = @_;
my ( @req, @opt );
# Refuse federation if rejected by user
if ( $self->param('confirm') == -1 ) {
if ( $req->param('confirm') == -1 ) {
my %h;
$h{$_} = undef foreach (
qw(fullname nickname language postcode timezone country gender email dob)
);
$self->updatePersistentSession( \%h );
$self->p->updatePersistentSession( \%h );
return 0;
}
......@@ -39,15 +39,13 @@ sub sregHook {
# Store policy if provided
if ( $k eq 'policy_url' ) {
if ( $v =~ Lemonldap::NG::Common::Regexp::HTTP_URI ) {
$self->{_openIdTrustExtMsg} .=
'<dl><dt>'
. $self->msg(PM_OPENID_PA)
. "&nbsp;:</dt><dd><a href=\"$v\">$v</a></dd></dl>";
$req->datas->{_openIdTrustExtMsg} .=
'<dl><dt trspan="openidPA">' . "&nbsp;:</dt><dd><a href=\"$v\">$v</a></dd></dl>";
# Question: is it important to notify policy changes ?
# if yes, uncomment this
#my $p =
# $self->{sessionInfo}->{"_openidTrust$trust_root\_Policy"};
# $req->{sessionInfo}->{"_openidTrust$trust_root\_Policy"};
#$accepted = 0 unless ( $p and $p eq $v );
}
else {
......@@ -64,7 +62,7 @@ sub sregHook {
# Parse optional attributes
elsif ( $k eq 'optional' ) {
$self->lmLog( "Optional attr $v", 'debug' );
push @opt, grep { defined $self->{"openIdSreg_$trust_root$_"} }
push @opt, grep { defined $self->conf->{"openIdSreg_$trust_root$_"} }
split( /,/, $v );
}
else {
......@@ -82,14 +80,13 @@ sub sregHook {
# If a required data is not available, returns nothing
foreach my $k (@req) {
unless ( $self->{"openIdSreg_$k"} ) {
unless ( $self->conf->{"openIdSreg_$k"} ) {
$self->lmLog(
"Parameter $k is required by $trust_root but not defined in configuration",
'notice'
);
$self->info(
'<h3>' . sprintf( $self->msg(PM_OPENID_RPNS), $k ) . '</h3>' );
$self->info( qq'<h3 trspan="openidRpns,$k"></h3>');
return ( 0, {} );
}
}
......@@ -99,38 +96,40 @@ sub sregHook {
# Requested parameters: check if already agreed or confirm is set
foreach my $k (@req) {
my $agree = $self->{sessionInfo}->{"_openidTrust$trust_root\_$k"};
my $agree = $req->{sessionInfo}->{"_openidTrust$trust_root\_$k"};
if ($accepted) {
unless ( $self->param('confirm') or $agree ) {
unless ( $req->param('confirm') or $agree ) {
$accepted = 0;
}
elsif ( !$agree ) {
$toStore{"_openidTrust$trust_root\_$k"} = 1;
}
}
$self->{"openIdSreg_$k"} =~ s/^\$//;
my $tmp = $self->conf->{"openIdSreg_$k"};
$tmp =~ s/^\$//;
$msg{req}->{$k} = $r{$k} =
$self->{sessionInfo}->{ $self->{"openIdSreg_$k"} } || '';
$req->{sessionInfo}->{ $self->{"openIdSreg_$k"} } || '';
}
# Optional parameters:
foreach my $k (@opt) {
$self->{"openIdSreg_$k"} =~ s/^\$//;
my $agree = $self->{sessionInfo}->{"_openidTrust$trust_root\_$k"};
my $tmp = $self->conf->{"openIdSreg_$k"};
$tmp =~ s/^\$//;
my $agree = $req->{sessionInfo}->{"_openidTrust$trust_root\_$k"};
if ($accepted) {
# First, check if already accepted
unless ( $self->param('confirm') or defined($agree) ) {
unless ( $req->param('confirm') or defined($agree) ) {
$accepted = 0;
$r{$k} = $self->{sessionInfo}->{ $self->{"openIdSreg_$k"} }
$r{$k} = $req->{sessionInfo}->{$tmp}
|| '';
}
# If confirmation is returned, check the value for this field
elsif ( $self->param('confirm') == 1 ) {
elsif ( $req->param('confirm') == 1 ) {
my $ck = 0;
if ( defined( $self->param("sreg_$k") ) ) {
$ck = ( $self->param("sreg_$k") eq 'OK' ) || 0;
if ( defined( $req->param("sreg_$k") ) ) {
$ck = ( $req->param("sreg_$k") eq 'OK' ) || 0;
}
# Store the value returned
......@@ -141,8 +140,7 @@ sub sregHook {
}
}
$msg{opt}->{$k} = $self->{sessionInfo}->{ $self->{"openIdSreg_$k"} }
|| '';
$msg{opt}->{$k} = $req->{sessionInfo}->{$tmp} || '';
# Store the value only if user agree it
if ($agree) {
......@@ -156,12 +154,12 @@ sub sregHook {
$ag{$k} = 0;
}
}
$self->updatePersistentSession( \%toStore ) if (%toStore);
$self->p->updatePersistentSession( \%toStore ) if (%toStore);
# Check if user has agreed request
if ($accepted) {
$self->_sub( 'userInfo',
$self->{sessionInfo}->{ $self->{whatToTrace} }
$self->p->userInfo(
$req->{sessionInfo}->{ $self->conf->{whatToTrace} }
. " has accepted OpenID SREG exchange with $trust_root" );
return ( 1, \%r );
}
......@@ -169,15 +167,14 @@ sub sregHook {
# else build message and return 0
else {
$self->{_openIdTrustExtMsg} .=
"<h3>" . $self->msg(PM_OPENID_AP) . "</h3>\n";
$req->datas->{_openIdTrustExtMsg} .= qq'<h3 trspan="openidAp"></h3>';
$self->{_openIdTrustExtMsg} .= "<table class=\"openidsreg\">\n";
$req->datas->{_openIdTrustExtMsg} .= "<table class=\"openidsreg\">\n";
# No choice for requested parameters: just an information
foreach my $k (@req) {
utf8::decode( $msg{req}->{$k} );
$self->{_openIdTrustExtMsg} .=
$req->datas->{_openIdTrustExtMsg} .=
"<tr class=\"required\">\n" . "<td>"
. "<input type=\"checkbox\" disabled=\"disabled\" checked=\"checked\"/>"
. "</td>\n"
......@@ -190,7 +187,7 @@ sub sregHook {
# For optional parameters: checkboxes are displayed
foreach my $k (@opt) {
utf8::decode( $msg{opt}->{$k} );
$self->{_openIdTrustExtMsg} .=
$req->datas->{_openIdTrustExtMsg} .=
"<tr class=\"optional\">\n"
. "<td>\n"
. "<input type=\"checkbox\" value=\"OK\""
......@@ -203,7 +200,7 @@ sub sregHook {
. "</tr>\n";
}
$self->{_openIdTrustExtMsg} .= "</table>\n";
$req->datas->{_openIdTrustExtMsg} .= "</table>\n";
$self->lmLog( 'Building validation form', 'debug' );
return ( 0, $prm );
......
......@@ -24,10 +24,10 @@ use constant OPENID2_ID_SELECT => 'http://specs.openid.net/auth/2.0/identifier_s
? *OpenID::util::push_url_arg
: *Net::OpenID::Server::_push_url_arg;
## @cmethod Lemonldap::NG::Portal::OpenID::Server new(hash opts)
## @cmethod Lemonldap::NG::Portal::Lib::OpenID::Server new(hash opts)
# Call Net::OpenID::Server::new() and store extensions
# @param %opts Net::OpenID::Server options
# @return Lemonldap::NG::Portal::OpenID::Server new object
# @return Lemonldap::NG::Portal::Lib::OpenID::Server new object
sub new {
my $class = shift;
my $self = fields::new($class);
......@@ -52,7 +52,7 @@ sub extensions {
# setup
# @return (string $type, hashref parameters)
sub _mode_checkid {
my Lemonldap::NG::Portal::OpenID::Server $self = shift;
my Lemonldap::NG::Portal::Lib::OpenID::Server $self = shift;
my ( $mode, $redirect_for_setup ) = @_;
my $return_to = $self->args("openid.return_to");
......@@ -181,12 +181,12 @@ __END__
=head1 NAME
Lemonldap::NG::Portal::OpenID::Server - Add capability to manage extensions to
Lemonldap::NG::Portal::Lib::OpenID::Server - Add capability to manage extensions to
Net::OpenID::Server
=head1 DESCRIPTION
Lemonldap::NG::Portal::OpenID::Server adds capability to manage extensions to
Lemonldap::NG::Portal::Lib::OpenID::Server adds capability to manage extensions to
Net::OpenID::Server.
=head1 SEE ALSO
......
......@@ -90,11 +90,7 @@
"PM11":"Logout from service providers...",
"PM12":"Redirection in progress...",
"PM13":"Go back to service provider",
"PM16":"Do you want to authenticate yourself on %s ?",
"PM17":"Update Common Domain Cookie",
"PM18":"Parameter %s requested for federation isn't available",
"PM19":"Data usage policy is available at",
"PM20":"Do you agree to provide the following parameters?",
"PM22":"Your last logins",
"PM23":"Your last failed logins",
"accept":"Accept",
......@@ -157,7 +153,11 @@
"newPwdIs":"Your new password is",
"newPwdSentTo":"A confirmation has been sent to your mail address.",
"oidcConsent":"The application %s would like to know:",
"openidAp":"Do you agree to provide the following parameters?",
"openIdExample":"for example:http://myopenid.org/toto",
"openidExchange":"Do you want to authenticate yourself on %s ?",
"openidPA":"Data usage policy is available at",
"openidRpns":"Parameter %s requested for federation isn't available",
"openSessionSpace":"This space allow you to open a SSO session. This will help you to securely access to all applications authorized by your profil.",
"openSSOSession":"Open your SSO session",
"password": "Password",
......
......@@ -90,11 +90,7 @@
"PM11":"Déconnexion des services...",
"PM12":"Redirection en cours...",
"PM13":"Retourner sur le fournisseur de service",
"PM16":"Souhaitez-vous vous identifier sur le site %s ?",
"PM17":"Mise à jour du cookie de domaine commun",
"PM18":"Le paramètre %s exigé pour la fédération n'est pas disponible",
"PM19":"La politique d'utilisation des données est disponible ici",
"PM20":"Consentez-vous à communiquer les paramètres suivants&nbsp;?",
"PM22":"Vos dernières connexions",
"PM23":"Vos dernières connexions refusées",
"accept":"Accepter",
......@@ -157,7 +153,11 @@
"newPwdIs":"Votre nouveau mot de passe est",
"newPwdSentTo":"Une confirmation a été envoyée à votre adresse mail.",
"oidcConsent":"L'application %s voudrait connaître :",
"openidAp":"Consentez-vous à communiquer les paramètres suivants&nbsp;?",
"openIdExample":"par exemple :http://myopenid.org/toto",
"openidExchange":"Souhaitez-vous vous identifier sur le site %s ?",
"openidPA":"La politique d'utilisation des données est disponible ici",
"openidRpns":"Le paramètre %s exigé pour la fédération n'est pas disponible",
"openSessionSpace":"Cet espace vous permet d'ouvrir une session SSO. Celle-ci vous aidera à accéder de manière totalement sécurisée à l'ensemble des applications autorisées par votre profil utilisateur.",
"openSSOSession":"Ouvrir une session SSO",
"password": "Mot-de-passe",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment