OIDC in progress (#595)
This commit is contained in:
parent
5202cd6f7c
commit
cdbe7d89e9
|
@ -1,9 +1,6 @@
|
||||||
# Missing methods
|
* checkLogins in SAML
|
||||||
|
* 2nd arg for trspan
|
||||||
* info()
|
* verify activeTimer on/off on screen
|
||||||
|
|
||||||
# Other
|
|
||||||
|
|
||||||
* Verify `useSafeJail=0`
|
* Verify `useSafeJail=0`
|
||||||
* Finish IssuerGet logout (-> info())
|
* Finish IssuerGet logout (-> info())
|
||||||
* Import r5420 (ssl\_opts) to trunk
|
* Import r5420 (ssl\_opts) to trunk
|
||||||
|
|
|
@ -2,6 +2,7 @@ package Lemonldap::NG::Portal::Auth::OpenIDConnect;
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use Mouse;
|
use Mouse;
|
||||||
|
use MIME::Base64 qw/encode_base64 decode_base64/;
|
||||||
use Lemonldap::NG::Portal::Main::Constants qw(
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
||||||
PE_CONFIRM
|
PE_CONFIRM
|
||||||
PE_ERROR
|
PE_ERROR
|
||||||
|
@ -15,7 +16,7 @@ extends 'Lemonldap::NG::Portal::Auth::Base',
|
||||||
|
|
||||||
# INTERFACE
|
# INTERFACE
|
||||||
|
|
||||||
has oplist => ( is => 'rw', default => sub { [] } );
|
has opList => ( is => 'rw', default => sub { [] } );
|
||||||
has opNumber => ( is => 'rw', default => 0 );
|
has opNumber => ( is => 'rw', default => 0 );
|
||||||
|
|
||||||
# INITIALIZATION
|
# INITIALIZATION
|
||||||
|
@ -56,7 +57,7 @@ sub init {
|
||||||
class => "openidconnect",
|
class => "openidconnect",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
$self->oplist( [@list] );
|
$self->opList( [@list] );
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +69,8 @@ sub extractFormInfo {
|
||||||
# Check callback
|
# Check callback
|
||||||
if ( $req->param( $self->conf->{oidcRPCallbackGetParam} ) ) {
|
if ( $req->param( $self->conf->{oidcRPCallbackGetParam} ) ) {
|
||||||
|
|
||||||
$self->lmLog(
|
$self->lmLog( 'OpenIDConnect callback URI detected: ' . $req->uri,
|
||||||
'OpenIDConnect callback URI detected: ' . $req->uri, 'debug' );
|
'debug' );
|
||||||
|
|
||||||
# AuthN Response
|
# AuthN Response
|
||||||
my $state = $req->param('state');
|
my $state = $req->param('state');
|
||||||
|
@ -86,8 +87,6 @@ sub extractFormInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get OpenID Provider
|
# Get OpenID Provider
|
||||||
$self->lmLog( "################### TODO: verify this (Auth:52)",
|
|
||||||
'debug' );
|
|
||||||
my $op = $req->datas->{_oidcOPCurrent};
|
my $op = $req->datas->{_oidcOPCurrent};
|
||||||
|
|
||||||
unless ($op) {
|
unless ($op) {
|
||||||
|
@ -115,7 +114,8 @@ sub extractFormInfo {
|
||||||
my $code = $req->param("code");
|
my $code = $req->param("code");
|
||||||
my $auth_method =
|
my $auth_method =
|
||||||
$self->conf->{oidcOPMetaDataOptions}->{$op}
|
$self->conf->{oidcOPMetaDataOptions}->{$op}
|
||||||
->{oidcOPMetaDataOptionsTokenEndpointAuthMethod};
|
->{oidcOPMetaDataOptionsTokenEndpointAuthMethod}
|
||||||
|
|| 'client_secret_post';
|
||||||
|
|
||||||
my $content =
|
my $content =
|
||||||
$self->getAuthorizationCodeAccessToken( $req, $op, $code,
|
$self->getAuthorizationCodeAccessToken( $req, $op, $code,
|
||||||
|
@ -197,7 +197,7 @@ sub extractFormInfo {
|
||||||
$req->datas->{id_token} = $id_token;
|
$req->datas->{id_token} = $id_token;
|
||||||
|
|
||||||
$self->lmLog( "Found user_id: " . $user_id, 'debug' );
|
$self->lmLog( "Found user_id: " . $user_id, 'debug' );
|
||||||
$self->{user} = $user_id;
|
$req->user($user_id);
|
||||||
|
|
||||||
return PE_OK;
|
return PE_OK;
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ sub extractFormInfo {
|
||||||
|
|
||||||
# Auto select provider if there is only one
|
# Auto select provider if there is only one
|
||||||
if ( $self->opNumber == 1 ) {
|
if ( $self->opNumber == 1 ) {
|
||||||
$op = $self->opList->[0];
|
$op = $self->opList->[0]->{val};
|
||||||
$self->lmLog( "Selecting the only defined OP: $op", 'debug' );
|
$self->lmLog( "Selecting the only defined OP: $op", 'debug' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,28 +222,6 @@ sub extractFormInfo {
|
||||||
my $portalPath = $self->{portal};
|
my $portalPath = $self->{portal};
|
||||||
$portalPath =~ s#^https?://[^/]+/?#/#;
|
$portalPath =~ s#^https?://[^/]+/?#/#;
|
||||||
|
|
||||||
foreach ( @{ $self->oplist } ) {
|
|
||||||
my $name = $self->conf->{oidcOPMetaDataOptions}->{$_}
|
|
||||||
->{oidcOPMetaDataOptionsDisplayName};
|
|
||||||
my $icon = $self->conf->{oidcOPMetaDataOptions}->{$_}
|
|
||||||
->{oidcOPMetaDataOptionsIcon};
|
|
||||||
my $img_src;
|
|
||||||
|
|
||||||
if ($icon) {
|
|
||||||
$img_src =
|
|
||||||
( $icon =~ m#^https?://# )
|
|
||||||
? $icon
|
|
||||||
: $portalPath . "skins/common/" . $icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
push @list,
|
|
||||||
{
|
|
||||||
val => $_,
|
|
||||||
name => $name,
|
|
||||||
icon => $img_src,
|
|
||||||
class => "openidconnect",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
$req->datas->{list} = $self->opList;
|
$req->datas->{list} = $self->opList;
|
||||||
$req->datas->{confirmRemember} = 0;
|
$req->datas->{confirmRemember} = 0;
|
||||||
|
|
||||||
|
@ -261,13 +239,14 @@ sub extractFormInfo {
|
||||||
$self->lmLog( "Build OpenIDConnect AuthN Request", 'debug' );
|
$self->lmLog( "Build OpenIDConnect AuthN Request", 'debug' );
|
||||||
|
|
||||||
# Save state
|
# Save state
|
||||||
my $state = $self->storeState(qw/urldc checkLogins _oidcOPCurrent/);
|
my $state = $self->storeState( $req, qw/urldc checkLogins _oidcOPCurrent/ );
|
||||||
|
|
||||||
# Authorization Code Flow
|
# Authorization Code Flow
|
||||||
$req->urldc( $self->buildAuthorizationCodeAuthnRequest( $req, $op, $state ) );
|
$req->urldc(
|
||||||
|
$self->buildAuthorizationCodeAuthnRequest( $req, $op, $state ) );
|
||||||
$self->lmLog( "Redirect user to " . $self->{urldc}, 'debug' );
|
|
||||||
|
|
||||||
|
$self->lmLog( "Redirect user to " . $req->{urldc}, 'debug' );
|
||||||
|
$req->continue(1);
|
||||||
$req->steps( [] );
|
$req->steps( [] );
|
||||||
|
|
||||||
return PE_OK;
|
return PE_OK;
|
||||||
|
@ -284,10 +263,12 @@ sub setAuthSessionInfo {
|
||||||
$req->{sessionInfo}->{authenticationLevel} = $self->conf->{oidcAuthnLevel};
|
$req->{sessionInfo}->{authenticationLevel} = $self->conf->{oidcAuthnLevel};
|
||||||
|
|
||||||
$req->{sessionInfo}->{OpenIDConnect_OP} = $op;
|
$req->{sessionInfo}->{OpenIDConnect_OP} = $op;
|
||||||
$req->{sessionInfo}->{OpenIDConnect_access_token} = $req->datas->{access_token};
|
$req->{sessionInfo}->{OpenIDConnect_access_token} =
|
||||||
|
$req->datas->{access_token};
|
||||||
|
|
||||||
# Keep ID Token in session
|
# Keep ID Token in session
|
||||||
my $store_IDToken = $self->conf->{oidcOPMetaDataOptions}->{$op}->{oidcOPMetaDataOptionsStoreIDToken};
|
my $store_IDToken = $self->conf->{oidcOPMetaDataOptions}->{$op}
|
||||||
|
->{oidcOPMetaDataOptionsStoreIDToken};
|
||||||
if ($store_IDToken) {
|
if ($store_IDToken) {
|
||||||
$self->lmLog( "Store ID Token in session", 'debug' );
|
$self->lmLog( "Store ID Token in session", 'debug' );
|
||||||
$req->{sessionInfo}->{OpenIDConnect_IDToken} = $req->datas->{id_token};
|
$req->{sessionInfo}->{OpenIDConnect_IDToken} = $req->datas->{id_token};
|
||||||
|
@ -311,8 +292,11 @@ sub authLogout {
|
||||||
if ($endsession_endpoint) {
|
if ($endsession_endpoint) {
|
||||||
my $logout_url = $self->conf->{portal} . '?logout=1';
|
my $logout_url = $self->conf->{portal} . '?logout=1';
|
||||||
$req->urldc(
|
$req->urldc(
|
||||||
$self->buildLogoutRequest( $endsession_endpoint,
|
$self->buildLogoutRequest(
|
||||||
$self->{sessionInfo}->{OpenIDConnect_IDToken}, $logout_url ));
|
$endsession_endpoint,
|
||||||
|
$self->{sessionInfo}->{OpenIDConnect_IDToken}, $logout_url
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$self->lmLog(
|
$self->lmLog(
|
||||||
"OpenID Connect logout to $op will be done on " . $req->urldc,
|
"OpenID Connect logout to $op will be done on " . $req->urldc,
|
||||||
|
|
|
@ -116,12 +116,15 @@ sub run {
|
||||||
login_hint acr_valuesi request request_uri/
|
login_hint acr_valuesi request request_uri/
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if ( $req->param($param) ) {
|
||||||
$oidc_request->{$param} = $req->param($param);
|
$oidc_request->{$param} = $req->param($param);
|
||||||
$self->lmLog(
|
$self->lmLog(
|
||||||
"OIDC request parameter $param: " . $oidc_request->{$param},
|
"OIDC request parameter $param: "
|
||||||
|
. $oidc_request->{$param},
|
||||||
'debug'
|
'debug'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Detect requested flow
|
# Detect requested flow
|
||||||
my $response_type = $oidc_request->{'response_type'};
|
my $response_type = $oidc_request->{'response_type'};
|
||||||
|
@ -244,7 +247,7 @@ sub run {
|
||||||
# Check if user needs to be reauthenticated
|
# Check if user needs to be reauthenticated
|
||||||
my $reauthentication = 0;
|
my $reauthentication = 0;
|
||||||
my $prompt = $oidc_request->{'prompt'};
|
my $prompt = $oidc_request->{'prompt'};
|
||||||
if ( $prompt =~ /\blogin\b/ ) {
|
if ( $prompt and $prompt =~ /\blogin\b/ ) {
|
||||||
$self->lmLog(
|
$self->lmLog(
|
||||||
"Reauthentication requested by Relying Party in prompt parameter",
|
"Reauthentication requested by Relying Party in prompt parameter",
|
||||||
'debug'
|
'debug'
|
||||||
|
@ -439,10 +442,12 @@ sub run {
|
||||||
$ask_for_consent = 1 if ( $prompt =~ /\bconsent\b/ );
|
$ask_for_consent = 1 if ( $prompt =~ /\bconsent\b/ );
|
||||||
}
|
}
|
||||||
if ($ask_for_consent) {
|
if ($ask_for_consent) {
|
||||||
if ( $self->param('confirm') == 1 ) {
|
if ( $req->param('confirm')
|
||||||
$self->updatePersistentSession(
|
and $req->param('confirm') == 1 )
|
||||||
|
{
|
||||||
|
$self->p->updatePersistentSession(
|
||||||
{ "_oidc_consent_time_$rp" => time } );
|
{ "_oidc_consent_time_$rp" => time } );
|
||||||
$self->updatePersistentSession(
|
$self->p->updatePersistentSession(
|
||||||
{
|
{
|
||||||
"_oidc_consent_scope_$rp" =>
|
"_oidc_consent_scope_$rp" =>
|
||||||
$oidc_request->{'scope'}
|
$oidc_request->{'scope'}
|
||||||
|
@ -451,7 +456,9 @@ sub run {
|
||||||
$self->lmLog( "Consent given for Relying Party $rp",
|
$self->lmLog( "Consent given for Relying Party $rp",
|
||||||
'debug' );
|
'debug' );
|
||||||
}
|
}
|
||||||
elsif ( $req->param('confirm') == -1 ) {
|
elsif ( $req->param('confirm')
|
||||||
|
and $req->param('confirm') == -1 )
|
||||||
|
{
|
||||||
$self->lmLog(
|
$self->lmLog(
|
||||||
"User refused consent for Relying party $rp",
|
"User refused consent for Relying party $rp",
|
||||||
'debug' );
|
'debug' );
|
||||||
|
@ -471,7 +478,7 @@ sub run {
|
||||||
'debug' );
|
'debug' );
|
||||||
|
|
||||||
# Return error if prompt is none
|
# Return error if prompt is none
|
||||||
if ( $prompt =~ /\bnone\b/ ) {
|
if ( $prompt and $prompt =~ /\bnone\b/ ) {
|
||||||
$self->lmLog(
|
$self->lmLog(
|
||||||
"Consent is needed but prompt is none",
|
"Consent is needed but prompt is none",
|
||||||
'debug' );
|
'debug' );
|
||||||
|
@ -501,10 +508,10 @@ sub run {
|
||||||
}
|
}
|
||||||
|
|
||||||
# HERE
|
# HERE
|
||||||
$self->info('<div class="oidc_consent_message">');
|
$req->info('<div class="oidc_consent_message">');
|
||||||
$self->info( '<img src="' . $img_src . '" />' )
|
$req->info( '<img src="' . $img_src . '" />' )
|
||||||
if $img_src;
|
if $img_src;
|
||||||
$self->info(
|
$req->info(
|
||||||
qq'<h3 trspan="oidcConsent,$display_name">The application $display_name would like to know:</h3><ul>'
|
qq'<h3 trspan="oidcConsent,$display_name">The application $display_name would like to know:</h3><ul>'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -520,10 +527,10 @@ qq'<h3 trspan="oidcConsent,$display_name">The application $display_name would li
|
||||||
{
|
{
|
||||||
my $message = $scope_messages->{$requested_scope}
|
my $message = $scope_messages->{$requested_scope}
|
||||||
|| 'anotherInformation';
|
|| 'anotherInformation';
|
||||||
$self->info(
|
$req->info(
|
||||||
qq'<li trspan="$message ">$message</li>');
|
qq'<li trspan="$message ">$message</li>');
|
||||||
}
|
}
|
||||||
$self->info('</ul></div>');
|
$req->info('</ul></div>');
|
||||||
$req->datas->{activeTimer} = 0;
|
$req->datas->{activeTimer} = 0;
|
||||||
return PE_CONFIRM;
|
return PE_CONFIRM;
|
||||||
}
|
}
|
||||||
|
@ -833,7 +840,7 @@ qq'<h3 trspan="oidcConsent,$display_name">The application $display_name would li
|
||||||
|
|
||||||
# Ask consent for logout
|
# Ask consent for logout
|
||||||
if ( $req->param('confirm') ) {
|
if ( $req->param('confirm') ) {
|
||||||
if ( $self->param('confirm') == 1 ) {
|
if ( $req->param('confirm') == 1 ) {
|
||||||
my $apacheSession = $self->p->getApacheSession( $req->id );
|
my $apacheSession = $self->p->getApacheSession( $req->id );
|
||||||
$self->p->_deleteSession($apacheSession);
|
$self->p->_deleteSession($apacheSession);
|
||||||
}
|
}
|
||||||
|
@ -853,7 +860,7 @@ qq'<h3 trspan="oidcConsent,$display_name">The application $display_name would li
|
||||||
return $req->param('confirm') == 1 ? PE_LOGOUT_OK : PE_OK;
|
return $req->param('confirm') == 1 ? PE_LOGOUT_OK : PE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->info(
|
$req->info(
|
||||||
'<div class="oidc_logout_message"><h3 trspan="logoutConfirm">Do you want to logout?</h3></div>'
|
'<div class="oidc_logout_message"><h3 trspan="logoutConfirm">Do you want to logout?</h3></div>'
|
||||||
);
|
);
|
||||||
$req->datas->{activeTimer} = 0;
|
$req->datas->{activeTimer} = 0;
|
||||||
|
@ -867,6 +874,7 @@ qq'<h3 trspan="oidcConsent,$display_name">The application $display_name would li
|
||||||
# Handle token endpoint
|
# Handle token endpoint
|
||||||
sub token {
|
sub token {
|
||||||
my ( $self, $req ) = @_;
|
my ( $self, $req ) = @_;
|
||||||
|
$req->parseBody if($req->method =~ /^post$/i);
|
||||||
$self->lmLog( "URL detected as an OpenID Connect TOKEN URL", 'debug' );
|
$self->lmLog( "URL detected as an OpenID Connect TOKEN URL", 'debug' );
|
||||||
|
|
||||||
# Check authentication
|
# Check authentication
|
||||||
|
@ -903,7 +911,7 @@ sub token {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get code session
|
# Get code session
|
||||||
my $code = $self->param('code');
|
my $code = $req->param('code');
|
||||||
|
|
||||||
$self->lmLog( "OpenID Connect Code: $code", 'debug' );
|
$self->lmLog( "OpenID Connect Code: $code", 'debug' );
|
||||||
|
|
||||||
|
@ -1025,6 +1033,7 @@ sub token {
|
||||||
sub userInfo {
|
sub userInfo {
|
||||||
my ( $self, $req ) = @_;
|
my ( $self, $req ) = @_;
|
||||||
$self->lmLog( "URL detected as an OpenID Connect USERINFO URL", 'debug' );
|
$self->lmLog( "URL detected as an OpenID Connect USERINFO URL", 'debug' );
|
||||||
|
$req->parseBody if($req->method =~ /^post$/i);
|
||||||
|
|
||||||
my $access_token = $self->getEndPointAccessToken($req);
|
my $access_token = $self->getEndPointAccessToken($req);
|
||||||
|
|
||||||
|
@ -1081,6 +1090,7 @@ sub userInfo {
|
||||||
sub jwks {
|
sub jwks {
|
||||||
my ( $self, $req ) = @_;
|
my ( $self, $req ) = @_;
|
||||||
$self->lmLog( "URL detected as an OpenID Connect JWKS URL", 'debug' );
|
$self->lmLog( "URL detected as an OpenID Connect JWKS URL", 'debug' );
|
||||||
|
$req->parseBody if($req->method =~ /^post$/i);
|
||||||
|
|
||||||
my $jwks = { keys => [] };
|
my $jwks = { keys => [] };
|
||||||
|
|
||||||
|
@ -1210,10 +1220,11 @@ sub endSessionDone {
|
||||||
my ( $self, $req ) = @_;
|
my ( $self, $req ) = @_;
|
||||||
$self->lmLog( "URL detected as an OpenID Connect END SESSION URL",
|
$self->lmLog( "URL detected as an OpenID Connect END SESSION URL",
|
||||||
'debug' );
|
'debug' );
|
||||||
|
$req->parseBody if($req->method =~ /^post$/i);
|
||||||
$self->lmLog( "User is already logged out", 'debug' );
|
$self->lmLog( "User is already logged out", 'debug' );
|
||||||
|
|
||||||
my $post_logout_redirect_uri = $self->param('post_logout_redirect_uri');
|
my $post_logout_redirect_uri = $req->param('post_logout_redirect_uri');
|
||||||
my $state = $self->param('state');
|
my $state = $req->param('state');
|
||||||
|
|
||||||
if ($post_logout_redirect_uri) {
|
if ($post_logout_redirect_uri) {
|
||||||
|
|
||||||
|
@ -1234,6 +1245,7 @@ sub checkSession {
|
||||||
my ( $self, $req ) = @_;
|
my ( $self, $req ) = @_;
|
||||||
$self->lmLog( "URL detected as an OpenID Connect CHECK SESSION URL",
|
$self->lmLog( "URL detected as an OpenID Connect CHECK SESSION URL",
|
||||||
'debug' );
|
'debug' );
|
||||||
|
$req->parseBody if($req->method =~ /^post$/i);
|
||||||
|
|
||||||
my $portalPath = $self->{portal};
|
my $portalPath = $self->{portal};
|
||||||
$portalPath =~ s#^https?://[^/]+/?#/#;
|
$portalPath =~ s#^https?://[^/]+/?#/#;
|
||||||
|
|
|
@ -69,7 +69,7 @@ sub loadOPs {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract JSON data
|
# Extract JSON data
|
||||||
foreach ( keys %{ $self->{oidcOPMetaDataJSON} } ) {
|
foreach ( keys %{ $self->conf->{oidcOPMetaDataJSON} } ) {
|
||||||
$self->oidcOPList->{$_}->{conf} =
|
$self->oidcOPList->{$_}->{conf} =
|
||||||
$self->decodeJSON( $self->conf->{oidcOPMetaDataJSON}->{$_} );
|
$self->decodeJSON( $self->conf->{oidcOPMetaDataJSON}->{$_} );
|
||||||
$self->oidcOPList->{$_}->{jwks} =
|
$self->oidcOPList->{$_}->{jwks} =
|
||||||
|
@ -209,7 +209,7 @@ sub getCallbackUri {
|
||||||
|
|
||||||
my $callback_uri = $self->conf->{portal};
|
my $callback_uri = $self->conf->{portal};
|
||||||
$callback_uri .=
|
$callback_uri .=
|
||||||
( $self->{portal} =~ /\?/ )
|
( $self->conf->{portal} =~ /\?/ )
|
||||||
? '&' . $callback_get_param . '=1'
|
? '&' . $callback_get_param . '=1'
|
||||||
: '?' . $callback_get_param . '=1';
|
: '?' . $callback_get_param . '=1';
|
||||||
|
|
||||||
|
@ -693,12 +693,13 @@ sub getOpenIDConnectSession {
|
||||||
# corresponding session_id
|
# corresponding session_id
|
||||||
# @return State Session ID
|
# @return State Session ID
|
||||||
sub storeState {
|
sub storeState {
|
||||||
my ( $self, @data ) = @_;
|
my ( $self, $req, @data ) = @_;
|
||||||
|
|
||||||
# check if there are data to store
|
# check if there are data to store
|
||||||
my $infos;
|
my $infos;
|
||||||
foreach (@data) {
|
foreach (@data) {
|
||||||
$infos->{$_} = $self->{$_} if $self->{$_};
|
$infos->{$_} = $req->{$_} if $req->{$_};
|
||||||
|
$infos->{"datas_$_"} = $req->datas->{$_} if $req->datas->{$_};
|
||||||
}
|
}
|
||||||
return unless ($infos);
|
return unless ($infos);
|
||||||
|
|
||||||
|
@ -738,12 +739,16 @@ sub extractState {
|
||||||
|
|
||||||
# Push values in $self
|
# Push values in $self
|
||||||
foreach ( keys %{ $stateSession->data } ) {
|
foreach ( keys %{ $stateSession->data } ) {
|
||||||
next if $_ =~ /(type|_session_id|_utime)/;
|
next if $_ =~ /(type|_session_id|_session_kind|_utime)/;
|
||||||
if ( $req->can($_) ) {
|
my $tmp = $stateSession->data->{$_};
|
||||||
$req->$_( $stateSession->data->{$_} );
|
if (s/^datas_//) {
|
||||||
|
$req->datas->{$_} = $tmp;
|
||||||
|
}
|
||||||
|
elsif ( $req->can($_) ) {
|
||||||
|
$req->$_($tmp);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$req->datas->{$_} = $stateSession->data->{$_};
|
$self->lmLog( "Unknown request property $_, skipping", 'warn' );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1071,7 +1076,7 @@ sub getEndPointAuthenticationCredentials {
|
||||||
my ( $client_id, $client_secret );
|
my ( $client_id, $client_secret );
|
||||||
|
|
||||||
my $authorization = $req->authorization;
|
my $authorization = $req->authorization;
|
||||||
if ( $authorization =~ /^Basic (\w+)/i ) {
|
if ( $authorization and $authorization =~ /^Basic (\w+)/i ) {
|
||||||
$self->lmLog( "Method client_secret_basic used", 'debug' );
|
$self->lmLog( "Method client_secret_basic used", 'debug' );
|
||||||
eval {
|
eval {
|
||||||
( $client_id, $client_secret ) = split( /:/, decode_base64($1) );
|
( $client_id, $client_secret ) = split( /:/, decode_base64($1) );
|
||||||
|
|
|
@ -59,53 +59,11 @@ sub display {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 1. Good authentication
|
# 1. Authentication not complete
|
||||||
|
|
||||||
# 1.1 Case : there is a message to display
|
# 1.1 A notification has to be done (session is created but hidden and
|
||||||
if ( my $info = $req->info() ) {
|
|
||||||
$skinfile = 'info';
|
|
||||||
%templateParams = (
|
|
||||||
AUTH_ERROR_TYPE => $req->error_type,
|
|
||||||
MSG => $info,
|
|
||||||
URL => $req->{urldc},
|
|
||||||
HIDDEN_INPUTS => $self->buildHiddenForm(),
|
|
||||||
ACTIVE_TIMER => $req->datas->{activeTimer},
|
|
||||||
FORM_METHOD => $self->conf->{infoFormMethod},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
# 1.2 Redirection
|
|
||||||
elsif ( $req->{error} == PE_REDIRECT ) {
|
|
||||||
$skinfile = "redirect";
|
|
||||||
%templateParams = (
|
|
||||||
URL => $req->{urldc},
|
|
||||||
HIDDEN_INPUTS => $self->buildHiddenForm($req),
|
|
||||||
FORM_METHOD => $req->datas->{redirectFormMethod} || 'get',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
# 1.3 Case : display menu
|
|
||||||
elsif ( $req->error == PE_OK ) {
|
|
||||||
|
|
||||||
$skinfile = 'menu';
|
|
||||||
|
|
||||||
#utf8::decode($auth_user);
|
|
||||||
|
|
||||||
%templateParams = (
|
|
||||||
AUTH_USER => $req->{sessionInfo}->{ $self->conf->{portalUserAttr} },
|
|
||||||
NEWWINDOW => $self->conf->{portalOpenLinkInNewWindow},
|
|
||||||
LOGOUT_URL => $self->conf->{portal} . "?logout=1",
|
|
||||||
APPSLIST_ORDER => $req->{sessionInfo}->{'appsListOrder'},
|
|
||||||
PING => $self->conf->{portalPingInterval},
|
|
||||||
$self->menu->params($req),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
# 2. Authentication not complete
|
|
||||||
|
|
||||||
# 2.1 A notification has to be done (session is created but hidden and
|
|
||||||
# unusable until the user has accept the message)
|
# unusable until the user has accept the message)
|
||||||
elsif ( my $notif = $req->datas->{notification} ) {
|
if ( my $notif = $req->datas->{notification} ) {
|
||||||
$skinfile = 'notification';
|
$skinfile = 'notification';
|
||||||
%templateParams = (
|
%templateParams = (
|
||||||
AUTH_ERROR_TYPE => $req->error_type,
|
AUTH_ERROR_TYPE => $req->error_type,
|
||||||
|
@ -117,7 +75,7 @@ sub display {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2.2 An authentication (or userDB) module needs to ask a question
|
# 1.2 An authentication (or userDB) module needs to ask a question
|
||||||
# before processing to the request
|
# before processing to the request
|
||||||
elsif ( $req->{error} == PE_CONFIRM ) {
|
elsif ( $req->{error} == PE_CONFIRM ) {
|
||||||
$skinfile = 'confirm';
|
$skinfile = 'confirm';
|
||||||
|
@ -140,8 +98,8 @@ sub display {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2.3 There is a message to disp->conf->conflay
|
# 1.3 There is a message to display
|
||||||
elsif ( $info = $req->info ) {
|
elsif ( my $info = $req->info ) {
|
||||||
$skinfile = 'info';
|
$skinfile = 'info';
|
||||||
%templateParams = (
|
%templateParams = (
|
||||||
AUTH_ERROR => $self->error,
|
AUTH_ERROR => $self->error,
|
||||||
|
@ -156,7 +114,7 @@ sub display {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2.4 OpenID menu page
|
# 1.4 OpenID menu page
|
||||||
elsif ($req->{error} == PE_OPENID_EMPTY
|
elsif ($req->{error} == PE_OPENID_EMPTY
|
||||||
or $req->{error} == PE_OPENID_BADID )
|
or $req->{error} == PE_OPENID_BADID )
|
||||||
{
|
{
|
||||||
|
@ -175,7 +133,36 @@ sub display {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2.5 Authentication has been refused OR this is the first access
|
# 2. Good authentication
|
||||||
|
|
||||||
|
# 2.1 Redirection
|
||||||
|
elsif ( $req->{error} == PE_REDIRECT ) {
|
||||||
|
$skinfile = "redirect";
|
||||||
|
%templateParams = (
|
||||||
|
URL => $req->{urldc},
|
||||||
|
HIDDEN_INPUTS => $self->buildHiddenForm($req),
|
||||||
|
FORM_METHOD => $req->datas->{redirectFormMethod} || 'get',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2.2 Case : display menu
|
||||||
|
elsif ( $req->error == PE_OK ) {
|
||||||
|
|
||||||
|
$skinfile = 'menu';
|
||||||
|
|
||||||
|
#utf8::decode($auth_user);
|
||||||
|
|
||||||
|
%templateParams = (
|
||||||
|
AUTH_USER => $req->{sessionInfo}->{ $self->conf->{portalUserAttr} },
|
||||||
|
NEWWINDOW => $self->conf->{portalOpenLinkInNewWindow},
|
||||||
|
LOGOUT_URL => $self->conf->{portal} . "?logout=1",
|
||||||
|
APPSLIST_ORDER => $req->{sessionInfo}->{'appsListOrder'},
|
||||||
|
PING => $self->conf->{portalPingInterval},
|
||||||
|
$self->menu->params($req),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3 Authentication has been refused OR this is the first access
|
||||||
else {
|
else {
|
||||||
$skinfile = 'login';
|
$skinfile = 'login';
|
||||||
my $login = $self->userId($req);
|
my $login = $self->userId($req);
|
||||||
|
|
|
@ -107,10 +107,10 @@ sub _forAuthUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _pForAuthUser {
|
sub _pForAuthUser {
|
||||||
my ( $self, $req ) = @_;
|
my ( $self, $req, @path ) = @_;
|
||||||
$self->lmLog( 'Parsing posted datas', 'debug' );
|
$self->lmLog( 'Parsing posted datas', 'debug' );
|
||||||
$req->parseBody;
|
$req->parseBody;
|
||||||
return $self->_forAuthUser($req);
|
return $self->_forAuthUser( $req, @path );
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -52,9 +52,28 @@ sub importHandlerDatas {
|
||||||
PE_OK;
|
PE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verify url parameter
|
# Verify url and confirm parameter
|
||||||
sub controlUrl {
|
sub controlUrl {
|
||||||
my ( $self, $req ) = @_;
|
my ( $self, $req ) = @_;
|
||||||
|
if ( my $c = $req->param('confirm') ) {
|
||||||
|
|
||||||
|
# Replace confirm stamp by 1 or -1
|
||||||
|
$c =~ s/^(-?)(.*)$/${1}1/;
|
||||||
|
|
||||||
|
# Decrypt confirm stamp if cipher available
|
||||||
|
# and confirm not already decrypted
|
||||||
|
if ( $self->conf->{cipher} and $2 ne "1" ) {
|
||||||
|
my $time = time() - $self->conf->{cipher}->decrypt($2);
|
||||||
|
if ( $time < 600 ) {
|
||||||
|
$self->lmLog( "Confirm parameter accepted $c", 'debug' );
|
||||||
|
$req->param( 'confirm', $c );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->lmLog( 'Confirmation to old, refused', 'notice' );
|
||||||
|
$req->param( 'confirm', 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
$req->{datas}->{_url} ||= '';
|
$req->{datas}->{_url} ||= '';
|
||||||
if ( my $url = $req->param('url') ) {
|
if ( my $url = $req->param('url') ) {
|
||||||
|
|
||||||
|
|
|
@ -50,9 +50,6 @@ has checkLogins => ( is => 'rw' );
|
||||||
# Boolean to indicate that url isn't Base64 encoded
|
# Boolean to indicate that url isn't Base64 encoded
|
||||||
has urlNotBase64 => ( is => 'rw' );
|
has urlNotBase64 => ( is => 'rw' );
|
||||||
|
|
||||||
# Info to display at login
|
|
||||||
has info => ( is => 'rw' );
|
|
||||||
|
|
||||||
# Menu error
|
# Menu error
|
||||||
has menuError => ( is => 'rw' );
|
has menuError => ( is => 'rw' );
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ extends 'Lemonldap::NG::Common::Module', 'Lemonldap::NG::Portal::Lib::OpenIDConn
|
||||||
|
|
||||||
sub init {
|
sub init {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
return $self->loadOPs;
|
||||||
}
|
}
|
||||||
|
|
||||||
# RUNNING METHODS
|
# RUNNING METHODS
|
||||||
|
|
|
@ -7,7 +7,7 @@ BEGIN {
|
||||||
require 't/test-lib.pm';
|
require 't/test-lib.pm';
|
||||||
}
|
}
|
||||||
|
|
||||||
my $debug = 'error';
|
my $debug = 'debug';
|
||||||
my ( $issuer, $sp, $res );
|
my ( $issuer, $sp, $res );
|
||||||
my %handlerOR = ( issuer => [], sp => [] );
|
my %handlerOR = ( issuer => [], sp => [] );
|
||||||
|
|
||||||
|
@ -25,11 +25,105 @@ switch ('sp');
|
||||||
ok( $sp = sp( $jwks, $metadata ), 'RP portal' );
|
ok( $sp = sp( $jwks, $metadata ), 'RP portal' );
|
||||||
count(1);
|
count(1);
|
||||||
|
|
||||||
#print STDERR Dumper( $jwks, $metadata );
|
# Query RP for auth
|
||||||
|
ok( $res = $sp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
|
||||||
|
count(1);
|
||||||
|
my ( $url, $query ) =
|
||||||
|
expectRedirection( $res, qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# );
|
||||||
|
|
||||||
|
# Push request to OP
|
||||||
|
switch ('issuer');
|
||||||
|
ok( $res = $issuer->_get( $url, query => $query, accept => 'text/html' ),
|
||||||
|
'Push request to OP' );
|
||||||
|
count(1);
|
||||||
|
expectOK($res);
|
||||||
|
|
||||||
|
# Try to authenticate to IdP
|
||||||
|
$query = "user=dwho&password=dwho&$query";
|
||||||
|
ok(
|
||||||
|
$res = $issuer->_post(
|
||||||
|
$url,
|
||||||
|
IO::String->new($query),
|
||||||
|
accept => 'text/html',
|
||||||
|
length => length($query),
|
||||||
|
),
|
||||||
|
'Post authentication'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
my $idpId = expectCookie($res);
|
||||||
|
my ( $host, $tmp );
|
||||||
|
( $host, $tmp, $query ) = expectForm( $res, '#' );
|
||||||
|
|
||||||
|
ok(
|
||||||
|
$res = $issuer->_post(
|
||||||
|
$url,
|
||||||
|
IO::String->new($query),
|
||||||
|
accept => 'text/html',
|
||||||
|
cookie => "lemonldap=$idpId",
|
||||||
|
length => length($query),
|
||||||
|
),
|
||||||
|
'Post authentication'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
|
||||||
|
($query) = expectRedirection( $res, qr#^http://auth.rp.com/?\?(.*)$# );
|
||||||
|
|
||||||
|
# Push OP response to RP
|
||||||
|
switch ('sp');
|
||||||
|
|
||||||
|
ok( $res = $sp->_get( '/', query => $query, accept => 'text/html' ),
|
||||||
|
'Call openidconnectcallback on RP' );
|
||||||
|
count(1);
|
||||||
|
my $rpId = expectCookie($res);
|
||||||
|
|
||||||
clean_sessions();
|
clean_sessions();
|
||||||
done_testing( count() );
|
done_testing( count() );
|
||||||
|
|
||||||
|
no warnings 'redefine';
|
||||||
|
|
||||||
|
sub LWP::UserAgent::request {
|
||||||
|
my ( $self, $req ) = @_;
|
||||||
|
ok( $req->uri =~ m#http://auth.((?:o|r)p).com(.*)#, 'REST request' );
|
||||||
|
my $host = $1;
|
||||||
|
my $url = $2;
|
||||||
|
my $res;
|
||||||
|
my $client = ( $host eq 'op' ? $issuer : $sp );
|
||||||
|
if ( $req->method =~ /^post$/i ) {
|
||||||
|
my $s = $req->content;
|
||||||
|
ok(
|
||||||
|
$res = $client->_post(
|
||||||
|
$url, IO::String->new($s),
|
||||||
|
length => length($s),
|
||||||
|
type => $req->header('Content-Type'),
|
||||||
|
),
|
||||||
|
'Execute request'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ok(
|
||||||
|
$res = $client->_get(
|
||||||
|
$url,
|
||||||
|
custom => {
|
||||||
|
HTTP_AUTHORIZATION => $req->header('Authorization'),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'Execute request'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expectOK($res);
|
||||||
|
ok( getHeader( $res, 'Content-Type' ) =~ m#^application/json#,
|
||||||
|
'Content is JSON' )
|
||||||
|
or explain( $res->[1], 'Content-Type => application/json' );
|
||||||
|
my $httpResp = HTTP::Response->new( $res->[0], 'OK' );
|
||||||
|
|
||||||
|
while ( my $name = shift @{ $res->[1] } ) {
|
||||||
|
$httpResp->header( $name, shift( @{ $res->[1] } ) );
|
||||||
|
}
|
||||||
|
$httpResp->content( join( '', @{ $res->[2] } ) );
|
||||||
|
count(3);
|
||||||
|
return $httpResp;
|
||||||
|
}
|
||||||
|
|
||||||
sub switch {
|
sub switch {
|
||||||
my $type = shift;
|
my $type = shift;
|
||||||
@Lemonldap::NG::Handler::Main::Reload::_onReload = @{
|
@Lemonldap::NG::Handler::Main::Reload::_onReload = @{
|
||||||
|
|
|
@ -66,6 +66,12 @@
|
||||||
"LockDirectory": "t/sessions/saml/lock",
|
"LockDirectory": "t/sessions/saml/lock",
|
||||||
"generateModule": "Lemonldap::NG::Common::Apache::Session::Generate::SHA256"
|
"generateModule": "Lemonldap::NG::Common::Apache::Session::Generate::SHA256"
|
||||||
},
|
},
|
||||||
|
"oidcStorage": "Apache::Session::File",
|
||||||
|
"oidcStorageOptions": {
|
||||||
|
"Directory": "t/sessions/saml",
|
||||||
|
"LockDirectory": "t/sessions/saml/lock",
|
||||||
|
"generateModule": "Lemonldap::NG::Common::Apache::Session::Generate::SHA256"
|
||||||
|
},
|
||||||
"reloadUrls": {},
|
"reloadUrls": {},
|
||||||
"userDB": "Demo",
|
"userDB": "Demo",
|
||||||
"whatToTrace": "_whatToTrace"
|
"whatToTrace": "_whatToTrace"
|
||||||
|
|
|
@ -48,33 +48,46 @@ sub expectRedirection {
|
||||||
my ( $res, $location ) = @_;
|
my ( $res, $location ) = @_;
|
||||||
ok( $res->[0] == 302, 'Get redirection' )
|
ok( $res->[0] == 302, 'Get redirection' )
|
||||||
or explain( $res->[0], 302 );
|
or explain( $res->[0], 302 );
|
||||||
count(2);
|
count(1);
|
||||||
if ( ref $location ) {
|
if ( ref $location ) {
|
||||||
my @match;
|
my @match;
|
||||||
@match = ( getRedirection($res) =~ $location );
|
@match = ( getRedirection($res) =~ $location );
|
||||||
ok( @match, 'Location header found' )
|
ok( @match, 'Location header found' )
|
||||||
or explain( $res->[1], "Location match: " . Dumper($location) );
|
or explain( $res->[1], "Location match: " . Dumper($location) );
|
||||||
|
count(1);
|
||||||
return @match;
|
return @match;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ok( getRedirection($res) eq $location, "Location is $location" )
|
ok( getRedirection($res) eq $location, "Location is $location" )
|
||||||
or explain( $res->[1], "Location => $location" );
|
or explain( $res->[1], "Location => $location" );
|
||||||
|
count(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub expectAutoPost {
|
sub expectAutoPost {
|
||||||
|
my @r = expectForm(@_);
|
||||||
|
my $method = pop @r;
|
||||||
|
ok ( $method =~ /^post$/i );
|
||||||
|
count(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub expectForm {
|
||||||
my ( $res, $hostRe, $uriRe, @requiredFields ) = @_;
|
my ( $res, $hostRe, $uriRe, @requiredFields ) = @_;
|
||||||
expectOK($res);
|
expectOK($res);
|
||||||
count(2);
|
count(1);
|
||||||
if (
|
if (
|
||||||
ok(
|
ok(
|
||||||
$res->[2]->[0] =~
|
$res->[2]->[0] =~
|
||||||
m@<form.+?action="(?:http://([^/]+)(/.*?)?|#)".+method="post"@s,
|
m@<form.+?action="(?:http://([^/]+)(/.*?)?|(#))".+method="(post|get)"@is,
|
||||||
'Form method is POST'
|
'Page contains a form'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
my ( $host, $uri ) = ( $1, $2 );
|
my ( $host, $uri, $hash, $method ) = ( $1, $2, $3, $4 );
|
||||||
|
if($hash and $hash eq '#') {
|
||||||
|
$host = '#';
|
||||||
|
$uri = '';
|
||||||
|
}
|
||||||
if ($hostRe) {
|
if ($hostRe) {
|
||||||
if ( ref $hostRe ) {
|
if ( ref $hostRe ) {
|
||||||
ok( $host =~ $hostRe, 'Host match' )
|
ok( $host =~ $hostRe, 'Host match' )
|
||||||
|
@ -96,7 +109,7 @@ sub expectAutoPost {
|
||||||
count(1);
|
count(1);
|
||||||
}
|
}
|
||||||
my %fields = ( $res->[2]->[0] =~
|
my %fields = ( $res->[2]->[0] =~
|
||||||
m#<input type="hidden".+?name="(.+?)".+?value="(.+?)"#s );
|
m#<input.+?name="(.+?)".+?value="(.+?)"#gs );
|
||||||
my $query = join( '&',
|
my $query = join( '&',
|
||||||
map { "$_=" . uri_escape( uri_unescape( $fields{$_} ) ) }
|
map { "$_=" . uri_escape( uri_unescape( $fields{$_} ) ) }
|
||||||
keys(%fields) );
|
keys(%fields) );
|
||||||
|
@ -254,6 +267,7 @@ sub logout {
|
||||||
|
|
||||||
sub _get {
|
sub _get {
|
||||||
my ( $self, $path, %args ) = @_;
|
my ( $self, $path, %args ) = @_;
|
||||||
|
print STDERR Data::Dumper::Dumper($args{custom});
|
||||||
return $self->app->(
|
return $self->app->(
|
||||||
{
|
{
|
||||||
'HTTP_ACCEPT' => $args{accept}
|
'HTTP_ACCEPT' => $args{accept}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user