Commit cdbe7d89 authored by Yadd's avatar Yadd

OIDC in progress (#595)

parent 5202cd6f
# 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; ...@@ -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', ...@@ -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 { ...@@ -56,7 +57,7 @@ sub init {
class => "openidconnect", class => "openidconnect",
}; };
} }
$self->oplist( [@list] ); $self->opList( [@list] );
return 1; return 1;
} }
...@@ -68,15 +69,15 @@ sub extractFormInfo { ...@@ -68,15 +69,15 @@ 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');
# Restore state # Restore state
if ($state) { if ($state) {
if ( $self->extractState($req, $state) ) { if ( $self->extractState( $req, $state ) ) {
$self->lmLog( "State $state extracted", 'debug' ); $self->lmLog( "State $state extracted", 'debug' );
} }
else { else {
...@@ -86,8 +87,6 @@ sub extractFormInfo { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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,29 +222,7 @@ sub extractFormInfo { ...@@ -222,29 +222,7 @@ sub extractFormInfo {
my $portalPath = $self->{portal}; my $portalPath = $self->{portal};
$portalPath =~ s#^https?://[^/]+/?#/#; $portalPath =~ s#^https?://[^/]+/?#/#;
foreach ( @{ $self->oplist } ) { $req->datas->{list} = $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->{confirmRemember} = 0; $req->datas->{confirmRemember} = 0;
$req->datas->{login} = 1; $req->datas->{login} = 1;
...@@ -261,14 +239,15 @@ sub extractFormInfo { ...@@ -261,14 +239,15 @@ 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' );
$req->steps([]); $self->lmLog( "Redirect user to " . $req->{urldc}, 'debug' );
$req->continue(1);
$req->steps( [] );
return PE_OK; return PE_OK;
} }
...@@ -279,15 +258,17 @@ sub authenticate { ...@@ -279,15 +258,17 @@ sub authenticate {
sub setAuthSessionInfo { sub setAuthSessionInfo {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
my $op = $req->datas->{_oidcOPCurrent}; my $op = $req->datas->{_oidcOPCurrent};
$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,11 +292,14 @@ sub authLogout { ...@@ -311,11 +292,14 @@ 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,
'debug' ); 'debug' );
} }
else { else {
......
...@@ -116,11 +116,14 @@ sub run { ...@@ -116,11 +116,14 @@ sub run {
login_hint acr_valuesi request request_uri/ login_hint acr_valuesi request request_uri/
) )
{ {
$oidc_request->{$param} = $req->param($param); if ( $req->param($param) ) {
$self->lmLog( $oidc_request->{$param} = $req->param($param);
"OIDC request parameter $param: " . $oidc_request->{$param}, $self->lmLog(
'debug' "OIDC request parameter $param: "
); . $oidc_request->{$param},
'debug'
);
}
} }
# Detect requested flow # Detect requested flow
...@@ -244,7 +247,7 @@ sub run { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -59,53 +59,11 @@ sub display {
}