From fe77ab4dbbcf62fff1e19b761a61898885c68236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Oudot?= Date: Mon, 1 Jun 2015 18:22:36 +0000 Subject: [PATCH] CHeck session iframe with CORS for session management (#184) --- .../lib/Lemonldap/NG/Manager/Attributes.pm | 5 ++ .../lib/Lemonldap/NG/Manager/Tree.pm | 1 + .../example/openid-configuration.pl | 3 + .../NG/Portal/IssuerDBOpenIDConnect.pm | 64 +++++++++++++++++++ .../lib/Lemonldap/NG/Portal/_OpenIDConnect.pm | 38 ++++++++++- 5 files changed, 110 insertions(+), 1 deletion(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm index 3d9e5025d..1b3df46fe 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm @@ -1956,6 +1956,11 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?: default => 'logout', documentation => 'OpenID Connect end session endpoint', }, + oidcServiceMetaDataCheckSessionURI => { + type => 'text', + default => 'checksession', + documentation => 'OpenID Connect check session iframe', + }, oidcServiceMetaDataAuthnContext => { type => 'keyTextContainer', default => { diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Tree.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Tree.pm index 91231586b..0f2d66a7b 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Tree.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Tree.pm @@ -817,6 +817,7 @@ sub tree { 'oidcServiceMetaDataJWKSURI', 'oidcServiceMetaDataRegistrationURI', 'oidcServiceMetaDataEndSessionURI', + 'oidcServiceMetaDataCheckSessionURI', ] }, 'oidcServiceMetaDataAuthnContext', diff --git a/lemonldap-ng-portal/example/openid-configuration.pl b/lemonldap-ng-portal/example/openid-configuration.pl index 85cafe029..e592f673f 100755 --- a/lemonldap-ng-portal/example/openid-configuration.pl +++ b/lemonldap-ng-portal/example/openid-configuration.pl @@ -13,6 +13,7 @@ my $userinfo_uri = $portal->{oidcServiceMetaDataUserInfoURI}; my $jwks_uri = $portal->{oidcServiceMetaDataJWKSURI}; my $registration_uri = $portal->{oidcServiceMetaDataRegistrationURI}; my $endsession_uri = $portal->{oidcServiceMetaDataEndSessionURI}; +my $checksession_uri = $portal->{oidcServiceMetaDataCheckSessionURI}; my ($path) = ( $issuerDBOpenIDConnectPath =~ /(\w+)/ ); my $issuer = $portal->{oidcServiceMetaDataIssuer}; @@ -31,6 +32,8 @@ $configuration->{registration_endpoint} = if ( $portal->{oidcServiceAllowDynamicRegistration} ); $configuration->{end_session_endpoint} = $issuer . $path . "/" . $endsession_uri; +$configuration->{check_session_iframe} = + $issuer . $path . "/" . $checksession_uri; $configuration->{scopes_supported} = [qw/openid profile email address phone/]; $configuration->{response_types_supported} = [ "code", diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm index 80e8af631..6adcd3772 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm @@ -37,6 +37,7 @@ sub issuerForUnAuthUser { my $jwks_uri = $self->{oidcServiceMetaDataJWKSURI}; my $registration_uri = $self->{oidcServiceMetaDataRegistrationURI}; my $endsession_uri = $self->{oidcServiceMetaDataEndSessionURI}; + my $checksession_uri = $self->{oidcServiceMetaDataCheckSessionURI}; my $issuer = $self->{oidcServiceMetaDataIssuer}; # Called URL @@ -543,6 +544,37 @@ sub issuerForUnAuthUser { return PE_LOGOUT_OK; } + # CHECK SESSION + if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${checksession_uri}# ) { + + $self->lmLog( + "URL $url detected as an OpenID Connect CHECK SESSION URL", + 'debug' ); + + print $self->header( + -type => 'text/html', + -access_control_allow_origin => '*' + ); + print $self->start_html( + -title => 'Check Session', + -script => [ + { + -type => 'text/javascript', + -src => +'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha256.js' + }, + { + -type => 'text/javascript', + -src => +'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js' + }, + { -code => $self->getSessionManagementOPIFrameJS } + ] + ); + print $self->end_html(); + $self->quit(); + } + PE_OK; } @@ -560,6 +592,7 @@ sub issuerForAuthUser { my $jwks_uri = $self->{oidcServiceMetaDataJWKSURI}; my $registration_uri = $self->{oidcServiceMetaDataRegistrationURI}; my $endsession_uri = $self->{oidcServiceMetaDataEndSessionURI}; + my $checksession_uri = $self->{oidcServiceMetaDataCheckSessionURI}; my $issuer = $self->{oidcServiceMetaDataIssuer}; # Session ID @@ -1343,6 +1376,37 @@ sub issuerForAuthUser { return PE_CONFIRM; } + # CHECK SESSION + if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${checksession_uri}# ) { + + $self->lmLog( + "URL $url detected as an OpenID Connect CHECK SESSION URL", + 'debug' ); + + print $self->header( + -type => 'text/html', + -access_control_allow_origin => '*' + ); + print $self->start_html( + -title => 'Check Session', + -script => [ + { + -type => 'text/javascript', + -src => +'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha256.js' + }, + { + -type => 'text/javascript', + -src => +'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js' + }, + { -code => $self->getSessionManagementOPIFrameJS } + ] + ); + print $self->end_html(); + $self->quit(); + } + PE_OK; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_OpenIDConnect.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_OpenIDConnect.pm index d43ad1561..c8e3e1f0a 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_OpenIDConnect.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_OpenIDConnect.pm @@ -1423,7 +1423,12 @@ sub createSessionState { my $salt = encode_base64url( $self->{cipher}->encrypt($client_id) ); my $data = $client_id . " " . $session_id . " " . $salt; - my $session_state = sha256_base64($data) . "." . $salt; + my $hash = sha256_base64($data); + while ( length($hash) % 4 ) { + $hash .= '='; + } + + my $session_state = $hash . "." . $salt; return $session_state; } @@ -1445,6 +1450,37 @@ sub getRequestJWT { return $response->decoded_content; } +## @method String getSessionManagementOPIFrameJS +# Create JS code needed on OP side to manage session +# return String JS code +sub getSessionManagementOPIFrameJS { + my ($self) = splice @_; + + my $js; + + $js .= "window.addEventListener(\"message\", receiveMessage, false);\n"; + $js .= "function receiveMessage(e){ \n"; + $js .= "var message = e.data; \n"; + $js .= "client_id = decodeURIComponent(message.split(' ')[0]);\n"; + $js .= "session_state = decodeURIComponent(message.split(' ')[1]);\n"; + $js .= "var salt = decodeURIComponent(session_state.split('.')[1]);\n"; + $js .= + 'var opbs = document.cookie.replace(/(?:(?:^|.*;\s*)' + . $self->{cookieName} + . '\s*\=\s*([^;]*).*$)|^.*$/, "$1");' . "\n"; + $js .= "var hash = CryptoJS.SHA256(client_id + ' ' + opbs + ' ' + salt);\n"; + $js .= "var ss = hash.toString(CryptoJS.enc.Base64) + '.' + salt;\n"; + $js .= "if (session_state == ss) {\n"; + $js .= "stat = 'unchanged';\n"; + $js .= "} else {\n"; + $js .= "stat = 'changed';\n"; + $js .= "}\n"; + $js .= "e.source.postMessage(stat,e.origin);\n"; + $js .= "};\n"; + + return $js; +} + 1; __END__