diff --git a/lemonldap-ng-common/scripts/lemonldap-ng-cli b/lemonldap-ng-common/scripts/lemonldap-ng-cli index 834c773c9..21556d21d 100755 --- a/lemonldap-ng-common/scripts/lemonldap-ng-cli +++ b/lemonldap-ng-common/scripts/lemonldap-ng-cli @@ -22,7 +22,7 @@ for ( my $i = 0 ; $i < @ARGV ; $i++ ) { $action ||= "help"; -if ( $action =~ /^(?:[gs]et|(?:add|del)Key)$/ ) { +if ( $action =~ /^(?:[gs]et|(?:add|del)Key|save|restore)$/ ) { eval { require Lemonldap::NG::Manager::Cli; }; die "Manager libraries not available, aborting ($@)" if ($@); Lemonldap::NG::Manager::Cli->run(@ARGV); @@ -61,18 +61,31 @@ lemonldap-ng-cli - Command-line manager for Lemonldap::NG web-SSO system. =head1 SYNOPSIS - # Get information about current configuration +Get information about current configuration + $ lemonldap-ng-cli info - # Update local configuration cache +Update local configuration cache + $ lemonldap-ng-cli update-cache - # Get some configuration parameter values +Save configuration + + $ lemonldap-ng-cli save >conf.json + +Restore configuration + + $ lemonldap-ng-cli restore conf.json + # OR + $ lemonldap-ng-cli restore - and L +=head2 Available commands + +=over + +=item info + +=item update-cache + +=item save + +=item restore + +=item get + +=item set + +=item addKey + +=item delKey + +=back + =head1 SEE ALSO L, L diff --git a/lemonldap-ng-handler/t/65-Lemonldap-NG-Handler-PSGI-ServiceToken.t b/lemonldap-ng-handler/t/65-Lemonldap-NG-Handler-PSGI-ServiceToken.t index bff8988a9..d333d74d7 100644 --- a/lemonldap-ng-handler/t/65-Lemonldap-NG-Handler-PSGI-ServiceToken.t +++ b/lemonldap-ng-handler/t/65-Lemonldap-NG-Handler-PSGI-ServiceToken.t @@ -41,6 +41,7 @@ ok( ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); count(2); +diag 'Waiting'; sleep 2; ok( @@ -54,6 +55,7 @@ ok( ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); count(2); +diag 'Waiting'; sleep 1; ok( @@ -67,6 +69,7 @@ ok( ok( $res->[0] == 302, 'Code is 200' ) or explain( $res->[0], 302 ); count(2); +diag 'Waiting'; sleep 1; ok( @@ -80,6 +83,7 @@ ok( ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); count(2); +diag 'Waiting'; sleep 1; ok( diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm index 329ca2f4b..a01425ccd 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm @@ -194,6 +194,31 @@ sub lastCfg { return $self->jsonResponse('/confs/latest')->{cfgNum}; } +sub save { + my ($self) = @_; + my $conf = $self->jsonResponse( '/confs/latest', 'full=1' ); + my $json = JSON->new->indent->canonical; + print $json->encode($conf); +} + +sub restore { + my ( $self, $file ) = @_; + require IO::String; + my $conf; + if ( $file eq '-' ) { + $conf = join '', ; + } + else { + open my $f, $file; + $conf = join '', <$f>; + close $f; + } + my $res = $self->_post( '/confs/raw', '', IO::String->new($conf), + 'application/json', length($conf) ); + use Data::Dumper; + print STDERR Dumper($res); +} + sub _getKey { my ( $self, $key ) = @_; my $sep = $self->sep; @@ -304,7 +329,7 @@ sub run { } $self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum ); my $action = shift; - unless ( $action =~ /^(?:get|set|addKey|delKey)$/ ) { + unless ( $action =~ /^(?:get|set|addKey|delKey|save|restore)$/ ) { die "unknown action $action. Only get, set, addKey or delKey are accepted"; } diff --git a/lemonldap-ng-manager/site/coffee/sessions.coffee b/lemonldap-ng-manager/site/coffee/sessions.coffee index 907b4b33e..c037ecb53 100644 --- a/lemonldap-ng-manager/site/coffee/sessions.coffee +++ b/lemonldap-ng-manager/site/coffee/sessions.coffee @@ -61,7 +61,7 @@ schemes = overScheme = _whatToTrace: (t,v,level,over) -> - if level == 1 and v.length > over + if level == 1 and v.length < max "#{t}=#{v}*&groupBy=substr(#{t},#{(level+over+1)})" else null @@ -70,6 +70,11 @@ overScheme = "#{t}=#{v}*&groupBy=net(#{t},#{16*level+4*(over+1)},2)" else null + _startTime: (t,v,level,over) -> + if level > 3 + "#{t}=#{v}*&groupBy=substr(#{t},#{(9+level+over+1)})" + else + null hiddenAttributes = '_password' @@ -410,7 +415,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', scheme = if schemes[$scope.type] schemes[$scope.type] - # - _updateTime must be displayed as startDate + # - _updateTime must be displayed as startTime else if $scope.type == '_updateTime' schemes._startTime diff --git a/lemonldap-ng-manager/site/htdocs/static/js/sessions.js b/lemonldap-ng-manager/site/htdocs/static/js/sessions.js index 8ff3bf858..50c03b177 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/sessions.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/sessions.js @@ -74,7 +74,7 @@ overScheme = { _whatToTrace: function(t, v, level, over) { - if (level === 1 && v.length > over) { + if (level === 1 && v.length < max) { return t + "=" + v + "*&groupBy=substr(" + t + "," + (level + over + 1) + ")"; } else { return null; @@ -86,6 +86,13 @@ } else { return null; } + }, + _startTime: function(t, v, level, over) { + if (level > 3) { + return t + "=" + v + "*&groupBy=substr(" + t + "," + (9 + level + over + 1) + ")"; + } else { + return null; + } } }; diff --git a/lemonldap-ng-manager/site/htdocs/static/js/sessions.min.js b/lemonldap-ng-manager/site/htdocs/static/js/sessions.min.js index fafadd1ea..ce37f3060 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/sessions.min.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/sessions.min.js @@ -1 +1 @@ -(function(){var categories,hiddenAttributes,llapp,max,menu,overScheme,schemes;max=25;schemes={_whatToTrace:[function(t,v){return"groupBy=substr("+t+",1)"},function(t,v){return t+"="+v+"*&groupBy="+t},function(t,v){return t+"="+v}],ipAddr:[function(t,v){return"groupBy=net("+t+",16,1)"},function(t,v){if(!v.match(/:/)){v=v+"."}return t+"="+v+"*&groupBy=net("+t+",32,2)"},function(t,v){if(!v.match(/:/)){v=v+"."}return t+"="+v+"*&groupBy=net("+t+",48,3)"},function(t,v){if(!v.match(/:/)){v=v+"."}return t+"="+v+"*&groupBy=net("+t+",128,4)"},function(t,v){return t+"="+v+"&groupBy=_whatToTrace"},function(t,v,q){return q.replace(/\&groupBy.*$/,"")+("&_whatToTrace="+v)}],_startTime:[function(t,v){return"groupBy=substr("+t+",8)"},function(t,v){return t+"="+v+"*&groupBy=substr("+t+",10)"},function(t,v){return t+"="+v+"*&groupBy=substr("+t+",11)"},function(t,v){return t+"="+v+"*&groupBy=substr("+t+",12)"},function(t,v){return t+"="+v+"*&groupBy=_whatToTrace"},function(t,v,q){console.log(t);console.log(v);console.log(q);return q.replace(/\&groupBy.*$/,"")+("&_whatToTrace="+v)}],doubleIp:[function(t,v){return t},function(t,v){return"_whatToTrace="+v+"&groupBy=ipAddr"},function(t,v,q){return q.replace(/\&groupBy.*$/,"")+("&ipAddr="+v)}]};overScheme={_whatToTrace:function(t,v,level,over){if(level===1&&v.length>over){return t+"="+v+"*&groupBy=substr("+t+","+(level+over+1)+")"}else{return null}},ipAddr:function(t,v,level,over){if(level>0&&level<4){return t+"="+v+"*&groupBy=net("+t+","+(16*level+4*(over+1))+",2)"}else{return null}}};hiddenAttributes="_password";categories={dateTitle:["_utime","_startTime","_updateTime","_lastAuthnUTime","_lastSeen"],connectionTitle:["ipAddr","_timezone","_url"],authenticationTitle:["_session_id","_user","_password","authenticationLevel"],modulesTitle:["_auth","_userDB","_passwordDB","_issuerDB","_authChoice","_authMulti","_userDBMulti"],saml:["_idp","_idpConfKey","_samlToken","_lassoSessionDump","_lassoIdentityDump"],groups:["groups","hGroups"],ldap:["dn"],BrowserID:["_browserIdAnswer","_browserIdAnswerRaw"],OpenIDConnect:["_oidc_id_token","_oidc_OP","_oidc_access_token"],sfaTitle:["_2fDevices"],oidcConsents:["_oidcConsents"]};menu={session:[{title:"deleteSession",icon:"trash"}],home:[]};llapp=angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]);llapp.controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function($scope,$translator,$location,$q,$http){var autoId,c,pathEvent,sessionType;$scope.links=links;$scope.menulinks=menulinks;$scope.staticPrefix=staticPrefix;$scope.scriptname=scriptname;$scope.formPrefix=formPrefix;$scope.impPrefix=impPrefix;$scope.sessionTTL=sessionTTL;$scope.availableLanguages=availableLanguages;$scope.waiting=true;$scope.showM=false;$scope.showT=true;$scope.data=[];$scope.currentScope=null;$scope.currentSession=null;$scope.menu=menu;$scope.translateP=$translator.translateP;$scope.translate=$translator.translate;$scope.translateTitle=function(node){return $translator.translateField(node,"title")};sessionType="global";$scope.menuClick=function(button){if(button.popup){window.open(button.popup)}else{if(!button.action){button.action=button.title}switch(typeof button.action){case"function":button.action($scope.currentNode,$scope);break;case"string":$scope[button.action]();break;default:console.log(typeof button.action)}}return $scope.showM=false};$scope.deleteOIDCConsent=function(rp,epoch){var item;item=angular.element(".data-"+epoch);item.remove();$scope.waiting=true;$http["delete"](scriptname+"sessions/OIDCConsent/"+sessionType+"/"+$scope.currentSession.id+"?rp="+rp+"&epoch="+epoch).then(function(response){return $scope.waiting=false},function(resp){return $scope.waiting=false});return $scope.showT=false};$scope.deleteSession=function(){$scope.waiting=true;return $http["delete"](scriptname+"sessions/"+sessionType+"/"+$scope.currentSession.id).then(function(response){$scope.currentSession=null;$scope.currentScope.remove();return $scope.waiting=false},function(resp){$scope.currentSession=null;$scope.currentScope.remove();return $scope.waiting=false})};$scope.stoggle=function(scope){var node;node=scope.$modelValue;if(node.nodes.length===0){$scope.updateTree(node.value,node.nodes,node.level,node.over,node.query,node.count)}return scope.toggle()};$scope.displaySession=function(scope){var sessionId,transformSession;transformSession=function(session){var _insert,array,attr,attrs,category,cv,element,epoch,i,id,j,k,key,l,len,len1,len2,len3,len4,len5,m,name,o,oidcConsent,p,real,ref,ref1,res,sfDevice,spoof,subres,time,title,tmp,value;_insert=function(re,title){var key,reg,tmp,value;tmp=[];reg=new RegExp(re);for(key in session){value=session[key];if(key.match(reg)&&value){tmp.push({title:key,value:value});delete session[key]}}if(tmp.length>0){return res.push({title:title,nodes:tmp})}};time=session._utime;id=session._session_id;for(key in session){value=session[key];if(!value){delete session[key]}else{if(typeof session==="string"&&value.match(/; /)){session[key]=value.split("; ")}if(typeof session[key]!=="object"){if(hiddenAttributes.match(new RegExp("\b"+key+"\b"))){session[key]="********"}else if(key.match(/^(_utime|_lastAuthnUTime|_lastSeen|notification)$/)){session[key]=$scope.localeDate(value)}else if(key.match(/^(_startTime|_updateTime)$/)){session[key]=$scope.strToLocaleDate(value)}}}}res=[];for(category in categories){attrs=categories[category];subres=[];for(i=0,len=attrs.length;i0){res.push({title:"__"+category+"__",nodes:subres})}}_insert("^openid","OpenID");_insert("^notification_(.+)","__notificationsDone__");if(session._loginHistory){tmp=[];if(session._loginHistory.successLogin){ref=session._loginHistory.successLogin;for(m=0,len3=ref.length;mb.title){return 1}else if(a.title real attribute");real.push(element)}else{spoof.push(element)}}tmp=spoof.concat(real);res.push({title:"__attributesAndMacros__",nodes:tmp});return{_utime:time,id:id,nodes:res}};$scope.currentScope=scope;sessionId=scope.$modelValue.session;$http.get(scriptname+"sessions/"+sessionType+"/"+sessionId).then(function(response){return $scope.currentSession=transformSession(response.data)});return $scope.showT=false};$scope.localeDate=function(s){var d;d=new Date(s*1e3);return d.toLocaleString()};$scope.isValid=function(epoch,type){var isValid,now,path;path=$location.path();now=Date.now()/1e3;console.log("Path",path);console.log("Session epoch",epoch);console.log("Current date",now);console.log("Session TTL",sessionTTL);isValid=now-epochmax&&overScheme[$scope.type]){if(tmp=overScheme[$scope.type]($scope.type,value,level,over,currentQuery)){over++;query=tmp;level=level-1}else{over=0}}else{over=0}return $http.get(scriptname+"sessions/"+sessionType+"?"+query).then(function(response){var data,i,len,n,ref;data=response.data;if(data.result){ref=data.values;for(i=0,len=ref.length;i0&&level<4){return t+"="+v+"*&groupBy=net("+t+","+(16*level+4*(over+1))+",2)"}else{return null}},_startTime:function(t,v,level,over){if(level>3){return t+"="+v+"*&groupBy=substr("+t+","+(9+level+over+1)+")"}else{return null}}};hiddenAttributes="_password";categories={dateTitle:["_utime","_startTime","_updateTime","_lastAuthnUTime","_lastSeen"],connectionTitle:["ipAddr","_timezone","_url"],authenticationTitle:["_session_id","_user","_password","authenticationLevel"],modulesTitle:["_auth","_userDB","_passwordDB","_issuerDB","_authChoice","_authMulti","_userDBMulti"],saml:["_idp","_idpConfKey","_samlToken","_lassoSessionDump","_lassoIdentityDump"],groups:["groups","hGroups"],ldap:["dn"],BrowserID:["_browserIdAnswer","_browserIdAnswerRaw"],OpenIDConnect:["_oidc_id_token","_oidc_OP","_oidc_access_token"],sfaTitle:["_2fDevices"],oidcConsents:["_oidcConsents"]};menu={session:[{title:"deleteSession",icon:"trash"}],home:[]};llapp=angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]);llapp.controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function($scope,$translator,$location,$q,$http){var autoId,c,pathEvent,sessionType;$scope.links=links;$scope.menulinks=menulinks;$scope.staticPrefix=staticPrefix;$scope.scriptname=scriptname;$scope.formPrefix=formPrefix;$scope.impPrefix=impPrefix;$scope.sessionTTL=sessionTTL;$scope.availableLanguages=availableLanguages;$scope.waiting=true;$scope.showM=false;$scope.showT=true;$scope.data=[];$scope.currentScope=null;$scope.currentSession=null;$scope.menu=menu;$scope.translateP=$translator.translateP;$scope.translate=$translator.translate;$scope.translateTitle=function(node){return $translator.translateField(node,"title")};sessionType="global";$scope.menuClick=function(button){if(button.popup){window.open(button.popup)}else{if(!button.action){button.action=button.title}switch(typeof button.action){case"function":button.action($scope.currentNode,$scope);break;case"string":$scope[button.action]();break;default:console.log(typeof button.action)}}return $scope.showM=false};$scope.deleteOIDCConsent=function(rp,epoch){var item;item=angular.element(".data-"+epoch);item.remove();$scope.waiting=true;$http["delete"](scriptname+"sessions/OIDCConsent/"+sessionType+"/"+$scope.currentSession.id+"?rp="+rp+"&epoch="+epoch).then(function(response){return $scope.waiting=false},function(resp){return $scope.waiting=false});return $scope.showT=false};$scope.deleteSession=function(){$scope.waiting=true;return $http["delete"](scriptname+"sessions/"+sessionType+"/"+$scope.currentSession.id).then(function(response){$scope.currentSession=null;$scope.currentScope.remove();return $scope.waiting=false},function(resp){$scope.currentSession=null;$scope.currentScope.remove();return $scope.waiting=false})};$scope.stoggle=function(scope){var node;node=scope.$modelValue;if(node.nodes.length===0){$scope.updateTree(node.value,node.nodes,node.level,node.over,node.query,node.count)}return scope.toggle()};$scope.displaySession=function(scope){var sessionId,transformSession;transformSession=function(session){var _insert,array,attr,attrs,category,cv,element,epoch,i,id,j,k,key,l,len,len1,len2,len3,len4,len5,m,name,o,oidcConsent,p,real,ref,ref1,res,sfDevice,spoof,subres,time,title,tmp,value;_insert=function(re,title){var key,reg,tmp,value;tmp=[];reg=new RegExp(re);for(key in session){value=session[key];if(key.match(reg)&&value){tmp.push({title:key,value:value});delete session[key]}}if(tmp.length>0){return res.push({title:title,nodes:tmp})}};time=session._utime;id=session._session_id;for(key in session){value=session[key];if(!value){delete session[key]}else{if(typeof session==="string"&&value.match(/; /)){session[key]=value.split("; ")}if(typeof session[key]!=="object"){if(hiddenAttributes.match(new RegExp("\b"+key+"\b"))){session[key]="********"}else if(key.match(/^(_utime|_lastAuthnUTime|_lastSeen|notification)$/)){session[key]=$scope.localeDate(value)}else if(key.match(/^(_startTime|_updateTime)$/)){session[key]=$scope.strToLocaleDate(value)}}}}res=[];for(category in categories){attrs=categories[category];subres=[];for(i=0,len=attrs.length;i0){res.push({title:"__"+category+"__",nodes:subres})}}_insert("^openid","OpenID");_insert("^notification_(.+)","__notificationsDone__");if(session._loginHistory){tmp=[];if(session._loginHistory.successLogin){ref=session._loginHistory.successLogin;for(m=0,len3=ref.length;mb.title){return 1}else if(a.title real attribute");real.push(element)}else{spoof.push(element)}}tmp=spoof.concat(real);res.push({title:"__attributesAndMacros__",nodes:tmp});return{_utime:time,id:id,nodes:res}};$scope.currentScope=scope;sessionId=scope.$modelValue.session;$http.get(scriptname+"sessions/"+sessionType+"/"+sessionId).then(function(response){return $scope.currentSession=transformSession(response.data)});return $scope.showT=false};$scope.localeDate=function(s){var d;d=new Date(s*1e3);return d.toLocaleString()};$scope.isValid=function(epoch,type){var isValid,now,path;path=$location.path();now=Date.now()/1e3;console.log("Path",path);console.log("Session epoch",epoch);console.log("Current date",now);console.log("Session TTL",sessionTTL);isValid=now-epochmax&&overScheme[$scope.type]){if(tmp=overScheme[$scope.type]($scope.type,value,level,over,currentQuery)){over++;query=tmp;level=level-1}else{over=0}}else{over=0}return $http.get(scriptname+"sessions/"+sessionType+"?"+query).then(function(response){var data,i,len,n,ref;data=response.data;if(data.result){ref=data.values;for(i=0,len=ref.length;i ( @@ -119,17 +122,38 @@ sub check { } if ( $user eq $req->{user} or !$user ) { - $self->userLogger->notice("Retrieve session from Sessions database"); + $self->logger->debug("checkUser requested for myself"); + $self->userLogger->notice("Return userData..."); $self->userLogger->warn("Using spoofed SSO groups if exist!!!") if ( $self->conf->{impersonationRule} ); $attrs = $req->userData; + $user = $req->{user}; } else { - $self->logger->debug("checkUser requested for $req->{user}"); - $req->{user} = $user; - $self->userLogger->notice( - "Retrieve session from userDB and compute Groups & Macros"); - $attrs = $self->_userDatas($req); + $self->logger->debug("checkUser requested for $user"); + + # Try to retrieve session from sessions DB + $self->userLogger->notice('Try to retrieve session from DB...'); + my $moduleOptions = $self->conf->{globalStorageOptions} || {}; + $moduleOptions->{backend} = $self->conf->{globalStorage}; + my $sessions = + $self->module->searchOn( $moduleOptions, $self->conf->{whatToTrace}, + $user ); + my $age = '1'; + foreach my $id ( keys %$sessions ) { + my $session = $self->p->getApacheSession($id) or next; + + if ( $session->{data}->{_utime} gt $age ) { + $attrs = $session->{data}; + $age = $session->{data}->{_utime}; + } + } + unless ( defined $attrs->{_session_id} ) { + $req->{user} = $user; + $self->userLogger->notice( + "NO session found in DB. Compute userData..."); + $attrs = $self->_userData($req); + } } if ( $req->error ) { @@ -204,11 +228,8 @@ sub check { LANGS => $self->conf->{showLanguages}, MSG => $msg, ALERTE => ( $msg eq 'checkUser' ? 'alert-info' : 'alert-warning' ), - LOGIN => ( - $self->p->checkXSSAttack( 'LOGIN', $req->{userData}->{uid} ) ? "" - : $req->{userData}->{uid} - ), - URL => ( + LOGIN => $user, + URL => ( $self->p->checkXSSAttack( 'URL', $url ) ? "" : $url ), @@ -300,10 +321,10 @@ sub _urlFormat { return lc("$proto$vhost$port") . "$appuri"; } -sub _userDatas { +sub _userData { my ( $self, $req ) = @_; - # Search user in database + # Compute session my $steps = [ 'getUser', 'setSessionInfo', 'setMacros', 'setGroups' ]; $self->conf->{checkUserDisplayPersistentInfo} ? push @$steps, 'setPersistentSessionInfo', 'setLocalGroups' @@ -328,7 +349,12 @@ sub _userDatas { $self->logger->debug('Identity not authorized'); return $req->error(PE_BADCREDENTIALS); } + unless ( defined $req->sessionInfo->{uid} ) { + # Avoid error with SAML, OIDC, etc... + $self->logger->debug("\"$req->{user}\" NOT found in userDB"); + return $req->error(PE_BADCREDENTIALS); + } $self->logger->debug("Return \"$req->{user}\" sessionInfo"); return $req->{sessionInfo}; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/UserDB/SAML.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/UserDB/SAML.pm index 2b72e7768..2ce8aed17 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/UserDB/SAML.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/UserDB/SAML.pm @@ -43,29 +43,34 @@ sub setSessionInfo { my $exportedAttr; # Force UTF-8 - my $force_utf8 = $self->conf->{samlIDPMetaDataOptions}->{$idpConfKey} - ->{samlIDPMetaDataOptionsForceUTF8}; + my $force_utf8 = + $self->conf->{samlIDPMetaDataOptions}->{$idpConfKey} + ->{samlIDPMetaDataOptionsForceUTF8} + if $idpConfKey; # Get all required attributes, not already set # in setAuthSessionInfo() - foreach ( - keys %{ $self->conf->{samlIDPMetaDataExportedAttributes}->{$idpConfKey} - } ) - { + if ($idpConfKey) { + foreach ( + keys + %{ $self->conf->{samlIDPMetaDataExportedAttributes}->{$idpConfKey} } + ) + { - # Extract fields from exportedAttr value - my ( $mandatory, $name, $format, $friendly_name ) = - split( /;/, - $self->conf->{samlIDPMetaDataExportedAttributes}->{$idpConfKey} - ->{$_} ); + # Extract fields from exportedAttr value + my ( $mandatory, $name, $format, $friendly_name ) = + split( /;/, + $self->conf->{samlIDPMetaDataExportedAttributes}->{$idpConfKey} + ->{$_} ); - # Keep mandatory attributes not sent in authentication response - if ( $mandatory and not defined $req->{sessionInfo}->{$_} ) { - $exportedAttr->{$_} = - $self->conf->{samlIDPMetaDataExportedAttributes}->{$idpConfKey} - ->{$_}; - $self->logger->debug( - "Attribute $_ will be requested to $idpConfKey"); + # Keep mandatory attributes not sent in authentication response + if ( $mandatory and not defined $req->{sessionInfo}->{$_} ) { + $exportedAttr->{$_} = + $self->conf->{samlIDPMetaDataExportedAttributes} + ->{$idpConfKey}->{$_}; + $self->logger->debug( + "Attribute $_ will be requested to $idpConfKey"); + } } } diff --git a/lemonldap-ng-portal/site/templates/common/mail_register_done.tpl b/lemonldap-ng-portal/site/templates/common/mail_register_done.tpl index d5fa2a43b..cca48225e 100644 --- a/lemonldap-ng-portal/site/templates/common/mail_register_done.tpl +++ b/lemonldap-ng-portal/site/templates/common/mail_register_done.tpl @@ -14,6 +14,6 @@ $password

-

Click here to access to portal

+

Click here to access to portal

diff --git a/lemonldap-ng-portal/t/61-BruteForceProtection.t b/lemonldap-ng-portal/t/61-BruteForceProtection.t index 600afb342..689b745c7 100644 --- a/lemonldap-ng-portal/t/61-BruteForceProtection.t +++ b/lemonldap-ng-portal/t/61-BruteForceProtection.t @@ -167,6 +167,8 @@ count(1); ok( $res->[2]->[0] =~ /<\/span>/, 'Rejected -> Protection enabled' ); count(1); + +diag 'Waiting'; sleep 1; ## Sixth failed connection -> Rejected @@ -184,6 +186,8 @@ count(1); ok( $res->[2]->[0] =~ /<\/span>/, 'Rejected -> Protection enabled' ); count(1); + +diag 'Waiting'; sleep 2; ## Sixth successful connection -> Rejected @@ -201,6 +205,8 @@ count(1); ok( $res->[2]->[0] =~ /<\/span>/, 'Rejected -> Protection enabled' ); count(1); + +diag 'Waiting'; sleep 3; ## Seventh successful connection -> Accepted diff --git a/lemonldap-ng-portal/t/61-Session-ActivityTimeout.t b/lemonldap-ng-portal/t/61-Session-ActivityTimeout.t index 5de50dc52..307a0ae73 100644 --- a/lemonldap-ng-portal/t/61-Session-ActivityTimeout.t +++ b/lemonldap-ng-portal/t/61-Session-ActivityTimeout.t @@ -33,6 +33,7 @@ expectOK($res); my $id1 = expectCookie($res); count(1); +diag 'Waiting'; sleep 3; ok( @@ -48,6 +49,7 @@ ok( $res->[2]->[0] =~ qr%Your applications%, or print STDERR Dumper( $res->[2]->[0] ); count(2); +diag 'Waiting'; sleep 5; ok( diff --git a/lemonldap-ng-portal/t/61-Session-Timeout.t b/lemonldap-ng-portal/t/61-Session-Timeout.t index c89cdff8a..7ab540f75 100644 --- a/lemonldap-ng-portal/t/61-Session-Timeout.t +++ b/lemonldap-ng-portal/t/61-Session-Timeout.t @@ -32,6 +32,7 @@ expectOK($res); my $id1 = expectCookie($res); count(1); +diag 'Waiting'; sleep 9; ok( @@ -47,6 +48,7 @@ ok( $res->[2]->[0] =~ qr%Your applications%, or print STDERR Dumper( $res->[2]->[0] ); count(2); +diag 'Waiting'; sleep 2; ok( diff --git a/lemonldap-ng-portal/t/67-CheckUser-with-issuer-SAML-POST.t b/lemonldap-ng-portal/t/67-CheckUser-with-issuer-SAML-POST.t new file mode 100644 index 000000000..0e7620b33 --- /dev/null +++ b/lemonldap-ng-portal/t/67-CheckUser-with-issuer-SAML-POST.t @@ -0,0 +1,638 @@ +use lib 'inc'; +use Test::More; +use strict; +use IO::String; +use LWP::UserAgent; +use LWP::Protocol::PSGI; +use MIME::Base64; + +BEGIN { + require 't/test-lib.pm'; + require 't/saml-lib.pm'; +} + +my $maintests = 24; +my $debug = 'error'; +my ( $issuer, $sp, $res ); +my %handlerOR = ( issuer => [], sp => [] ); + +# Redefine LWP methods for tests +LWP::Protocol::PSGI->register( + sub { + my $req = Plack::Request->new(@_); + fail('POST should not launch SOAP requests'); + count(1); + return [ 500, [], [] ]; + } +); + +SKIP: { + eval "use Lasso"; + if ($@) { + skip 'Lasso not found', $maintests; + } + + # Initialization + ok( $issuer = issuer(), 'Issuer portal' ); + $handlerOR{issuer} = \@Lemonldap::NG::Handler::Main::_onReload; + switch ('sp'); + &Lemonldap::NG::Handler::Main::cfgNum( 0, 0 ); + + ok( $sp = sp(), 'SP portal' ); + $handlerOR{sp} = \@Lemonldap::NG::Handler::Main::_onReload; + + # Simple SP access + my $res; + ok( + $res = $sp->_get( + '/', accept => 'text/html', + ), + 'Unauth SP request' + ); + expectOK($res); + ok( expectCookie( $res, 'lemonldapidp' ), 'IDP cookie defined' ) + or explain( + $res->[1], +'Set-Cookie => lemonldapidp=http://auth.idp.com/saml/metadata; domain=.sp.com; path=/' + ); + my ( $host, $url, $s ) = + expectAutoPost( $res, 'auth.idp.com', '/saml/singleSignOn', + 'SAMLRequest' ); + + # Push SAML request to IdP + switch ('issuer'); + ok( + $res = $issuer->_post( + $url, + IO::String->new($s), + accept => 'text/html', + length => length($s) + ), + 'Post SAML request to IdP' + ); + expectOK($res); + my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' ); + + # Try to authenticate with an unauthorized user to IdP + $s = "user=dwho&password=dwho&$s"; + ok( + $res = $issuer->_post( + $url, + IO::String->new($s), + accept => 'text/html', + cookie => $pdata, + length => length($s), + ), + 'Post authentication' + ); + ok( $res->[2]->[0] =~ /trmsg="89"/, 'Reject reason is 89' ) + or print STDERR Dumper( $res->[2]->[0] ); + + # Simple SP access + ok( + $res = $sp->_get( + '/', accept => 'text/html', + ), + 'Unauth SP request' + ); + expectOK($res); + ok( expectCookie( $res, 'lemonldapidp' ), 'IDP cookie defined' ) + or explain( + $res->[1], +'Set-Cookie => lemonldapidp=http://auth.idp.com/saml/metadata; domain=.sp.com; path=/' + ); + ( $host, $url, $s ) = + expectAutoPost( $res, 'auth.idp.com', '/saml/singleSignOn', + 'SAMLRequest' ); + + # Push SAML request to IdP + ok( + $res = $issuer->_post( + $url, + IO::String->new($s), + accept => 'text/html', + length => length($s) + ), + 'Post SAML request to IdP' + ); + expectOK($res); + $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' ); + + # Try to authenticate with an authorized user to IdP + $s = "user=davros&password=davros&$s"; + ok( + $res = $issuer->_post( + $url, + IO::String->new($s), + accept => 'text/html', + cookie => $pdata, + length => length($s), + ), + 'Post authentication' + ); + my $idpId = expectCookie($res); + + # Expect pdata to be cleared + $pdata = expectCookie( $res, 'lemonldappdata' ); + ok( $pdata !~ 'issuerRequestsaml', 'SAML request cleared from pdata' ); + + ( $host, $url, $s ) = + expectAutoPost( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost', + 'SAMLResponse' ); + + # Post SAML response to SP + switch ('sp'); + ok( + $res = $sp->_post( + $url, IO::String->new($s), + accept => 'text/html', + length => length($s), + cookie => 'lemonldapidp=http://auth.idp.com/saml/metadata', + ), + 'Post SAML response to SP' + ); + + # Verify authentication on SP + expectRedirection( $res, 'http://auth.sp.com' ); + my $spId = expectCookie($res); + + ok( + $res = + $sp->_get( '/', cookie => "lemonldap=$spId", accept => 'text/html' ), + 'Get / on SP' + ); + count(1); + expectOK($res); + expectAuthenticatedAs( $res, 'davros@badguy.org@idp' ); + + # Simple SP access + my $res; + ok( + $res = $sp->_get( + '/', accept => 'text/html', + ), + 'Unauth SP request' + ); + expectOK($res); + ok( expectCookie( $res, 'lemonldapidp' ), 'IDP cookie defined' ) + or explain( + $res->[1], +'Set-Cookie => lemonldapidp=http://auth.idp.com/saml/metadata; domain=.sp.com; path=/' + ); + my ( $host, $url, $s ) = + expectAutoPost( $res, 'auth.idp.com', '/saml/singleSignOn', + 'SAMLRequest' ); + + # Push SAML request to IdP + switch ('issuer'); + ok( + $res = $issuer->_post( + $url, + IO::String->new($s), + accept => 'text/html', + length => length($s) + ), + 'Post SAML request to IdP' + ); + expectOK($res); + my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' ); + + # Try to authenticate with an authorized user to IdP + $s = "user=french&password=french&$s"; + ok( + $res = $issuer->_post( + $url, + IO::String->new($s), + accept => 'text/html', + cookie => $pdata, + length => length($s), + ), + 'Post authentication' + ); + $idpId = expectCookie($res); + + # Expect pdata to be cleared + $pdata = expectCookie( $res, 'lemonldappdata' ); + ok( $pdata !~ 'issuerRequestsaml', 'SAML request cleared from pdata' ); + + ( $host, $url, $s ) = + expectAutoPost( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost', + 'SAMLResponse' ); + + # Post SAML response to SP + switch ('sp'); + ok( + $res = $sp->_post( + $url, IO::String->new($s), + accept => 'text/html', + length => length($s), + cookie => 'lemonldapidp=http://auth.idp.com/saml/metadata', + ), + 'Post SAML response to SP' + ); + + # Verify authentication on SP + expectRedirection( $res, 'http://auth.sp.com' ); + $spId = expectCookie($res); + + ok( + $res = + $sp->_get( '/', cookie => "lemonldap=$spId", accept => 'text/html' ), + 'Get / on SP' + ); + count(1); + expectOK($res); + expectAuthenticatedAs( $res, 'fa@badwolf.org@idp' ); + + # CheckUser form -> granted + # ------------------------ + ok( + $res = $sp->_get( + '/checkuser', + cookie => "lemonldap=$spId", + accept => 'text/html' + ), + 'CheckUser form', + ); + my ( $host, $url, $query ) = + expectForm( $res, undef, '/checkuser', 'user', 'url' ); + ok( $res->[2]->[0] =~ m%%, + 'Found trspan="checkUser"' ) + or explain( $res->[2]->[0], 'trspan="checkUser"' ); + ok( $res->[2]->[0] =~ m%uid%, + 'Found attribute uid' ) + or explain( $res->[2]->[0], 'Attribute uid' ); + ok( $res->[2]->[0] =~ m%french%, + 'Found value french' ) + or explain( $res->[2]->[0], 'Value french' ); + count(4); + + # CheckUser request with unknown user + $query =~ s/user=french/user=rtyler/; + ok( + $res = $sp->_post( + '/checkuser', + IO::String->new($query), + cookie => "lemonldap=$spId", + length => length($query), + accept => 'text/html', + ), + 'POST checkuser' + ); + ok( + $res->[2]->[0] =~ +m%
%, + ' PE5 found' + ) or explain( $res->[2]->[0], 'PE5 - Unknown identity' ); + count(2); + + # CheckUser request with an already authneticated user + $query =~ s/user=rtyler/user=davros/; + ok( + $res = $sp->_post( + '/checkuser', + IO::String->new($query), + cookie => "lemonldap=$spId", + length => length($query), + accept => 'text/html', + ), + 'POST checkuser' + ); + + my ( $host, $url, $query ) = + expectForm( $res, undef, '/checkuser', 'user', 'url' ); + ok( $res->[2]->[0] =~ m%%, + 'Found trspan="checkUser"' ) + or explain( $res->[2]->[0], 'trspan="checkUser"' ); + ok( $res->[2]->[0] =~ m%uid%, + 'Found attribute uid' ) + or explain( $res->[2]->[0], 'Attribute uid' ); + ok( $res->[2]->[0] =~ m%mail%, + 'Found attribute mail' ) + or explain( $res->[2]->[0], 'Attribute mail' ); + ok( $res->[2]->[0] =~ m%davros\@badguy.org%, + 'Found value davros@badguy.org' ) + or explain( $res->[2]->[0], 'Value davros@badguy.org' ); + count(5); + + # Logout initiated by SP + ok( + $res = $sp->_get( + '/', + query => 'logout', + cookie => "lemonldap=$spId", + accept => 'text/html' + ), + 'Query SP for logout' + ); + ( $host, $url, $s ) = + expectAutoPost( $res, 'auth.idp.com', '/saml/singleLogout', + 'SAMLRequest' ); + + # Push SAML logout request to IdP + switch ('issuer'); + ok( + $res = $issuer->_post( + $url, + IO::String->new($s), + accept => 'text/html', + cookie => "lemonldap=$idpId", + length => length($s) + ), + 'Post SAML logout request to IdP' + ); + ( $host, $url, $s ) = + expectAutoPost( $res, 'auth.sp.com', '/saml/proxySingleLogoutReturn', + 'SAMLResponse' ); + + # Post SAML response to SP + switch ('sp'); + ok( + $res = $sp->_post( + $url, IO::String->new($s), + accept => 'text/html', + length => length($s), + cookie => 'lemonldapidp=http://auth.idp.com/saml/metadata', + ), + 'Post SAML response to SP' + ); + expectRedirection( $res, 'http://auth.sp.com' ); + + # Test if logout is done + switch ('issuer'); + ok( + $res = $issuer->_get( + '/', cookie => "lemonldap=$idpId", + ), + 'Test if user is reject on IdP' + ); + expectReject($res); + + switch ('sp'); + ok( + $res = $sp->_get( + '/', + accept => 'text/html', + cookie => + "lemonldapidp=http://auth.idp.com/saml/metadata; lemonldap=$spId" + ), + 'Test if user is reject on SP' + ); + expectOK($res); + expectAutoPost( $res, 'auth.idp.com', '/saml/singleSignOn', 'SAMLRequest' ); +} + +count($maintests); +clean_sessions(); +done_testing( count() ); + +sub switch { + my $type = shift; + @Lemonldap::NG::Handler::Main::_onReload = @{ + $handlerOR{$type}; + }; +} + +sub issuer { + return LLNG::Manager::Test->new( { + ini => { + logLevel => $debug, + domain => 'idp.com', + portal => 'http://auth.idp.com', + authentication => 'Demo', + userDB => 'Same', + issuerDBSAMLActivation => 1, + issuerDBSAMLRule => '$uid =~ /(?:french|davros)/', + samlSPMetaDataOptions => { + 'sp.com' => { + samlSPMetaDataOptionsEncryptionMode => 'none', + samlSPMetaDataOptionsSignSSOMessage => 1, + samlSPMetaDataOptionsSignSLOMessage => 1, + samlSPMetaDataOptionsCheckSSOMessageSignature => 1, + samlSPMetaDataOptionsCheckSLOMessageSignature => 1, + } + }, + samlSPMetaDataExportedAttributes => { + 'sp.com' => { + cn => +'1;cn;urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + uid => +'1;uid;urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + } + }, + samlOrganizationDisplayName => "IDP", + samlOrganizationName => "IDP", + samlOrganizationURL => "http://www.idp.com/", + samlServicePrivateKeyEnc => "-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAnfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADT +csus5Xn3id5+8Q9TuMFsW9kIEeXiaPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46 +Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnVDNfSEASVIppEBYjDX203ypmURIzU +6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+tBlcnMrkv/40DSUkehQIl2JmlFrl2 +Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5NMd0KFa6CwZUUSHJqH5GFy5Y2yl4l +g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQIDAQABAoIBAHnfqjX3eO8SfnP5 +NURp90Td2mNHirCn0qLd9NKl1ySMPR1GgeH9SQ7Umu32EcteAUL5dOw2PiTZVmeW +cKINgsWVftXUQcOQ4xIqWKb51QUBdy0FhxrZRSFjWxXt5iYK1PmzHfsax/g1/S9C +RnqtFyjOy1bywkSt9jiy+9YBR2B7BDhLHlILbijWn5zaecaV4YA+L1UK4M/mehdb ++0FVPavbGpnlqBRTY+7YXfZ/mRPCfn5DvO9lW1O0pJMmNdBh9kmm3DxHf6AkK47a +43gO/dRWiWo2rZ/+Jw7uyqOb23U0MydP7kia0p3tzCUBPsrlgnichYG5RNFp0wqy +3VT1TYECgYEA0Y9vENy1jJd+s7WbGrsRtSKxfZgtJr0yjSlQVYrIlwbZSGn+ndxq +V2vVlwIgLX3pz6T40BMfk6SNx08jjy0Sgn6OAM0ILrinno8yWcSAMCmfCU0S/3O1 +55bqtcnk4XTHBHzJ5OrnrPaW5ourvJz0lcWEKMg3BXxLzaF6ZRy85nECgYEAwPMD +LNAKLCDrUMyYFOpPyPLe7wvszcFvPipGgerSgFP1c6N7xaMUdHDYqBfuis1khPGF +YcMHeNBYmzX6yEGbp3lrB4PHpUySmTU3mv3u9I05aahInK21gXum3uRkCWyyIF6V +T/qeszl9mVOCp0CC4eG3IMVpaD0UKDEHVhERYCkCgYAjuTPRyA4a3Wh38ilysRkf +q75eDqcDx5Tqg3RyYKo5NK2troP9HSnzpSpQB8i8eI53G0RfFCN5479XjqIdMi3J +mRFUCZ+vd0L7wKVwsBK6Ix49U6o9adhElnGEc9pUpLeYiD1SjMjZr1+iBYVNLeRz +86vH1/mpMbsqXrCis/dvwQKBgGttomHr/w3s0jftget7PirrFrbP0+wHfDGHhjRF +kyhCFtJovrwefYALaIXGtVjw3LusYZA570oT7pGUb2naJZkMYEwR0jG1vZWx7KDO +K6JbkxDB0pPxn7JVL2bAkPYyX8boAohCSOQO6WBZ/8+xem3bp4OGhpa0EyoBik0g +OaVpAoGATj4SyYsE10hGT676iie8zy3fi5IPC3E+x4QlVuusaLtuY8LJA50stjtx +gUa/JAKlZZL+gvzvOviQIxyfIChXOdTt5uiOYkdHJDbAF3NSrji7hrXq4v8UZv75 +8hBrwJZIpy6y01dRlrriHmPRtEq1pk7JX2uUg0sP5g4BEcsaCbc= +-----END RSA PRIVATE KEY----- +", + samlServicePrivateKeySig => "-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtR/wgDqWB4Maho5V6TjcL/NbNfjgIh7GcgkrB5RZcVT1GTej +JlMjUQdgBKBuZXQN+7/29P6UcGq1kYalURq6S8SpeJ1ofp5rBEoD/TIkvU0JOcid +65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRyBIQzB0SIxSpnrsigqNsE1E94toDM +x4wovjHu/9ABAImREV7Sz83OeFF00/sghrjTEJOD/gHf04JCn9MgNOqvSTysr9LX +Wg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5yD41mi+hT8Rh+W8Je8rsiML4VMxz +sb1l9303asw6suo5bLTISKNSbu1nt1NkpNxzywIDAQABAoIBAQCQkbvPPfP+bwC/ +IeEk1IO7qkzFWa7czR+safD0jc6OjTdNN4F716Q6yt4zEzLKu8VliiW+C23EBQiD +7asKf4DvdTun0ExVtHDK7aEdeealSlXwz1ZtdypyILbtq1UGo/rR0v4x601rQPl0 +IrBmFf6D6FkqleNtLJmxguXpoVfLdYKNwkxH2ux+GOA9r2o5pUCQmJGDap5YWRuQ +uB71ewJjVWujaL3e1ac/5cP7/tqWmgAiOaN8sYdD6+oWOR47bHj8JKcMBSl4y2QC +dL31cGmmf5KqBbtISki3RXfHHjT7E3Z85CbESkKTZlEb1ar3XmepY6Z7V5UO16oz +fFE5R6khAoGBAOl9Qb+qYVVO5ugE65ORjYVeuXykANhM9ssiY5a6zuAakWzw7Zv3 +k6PXm9p7azlEXAlTnTXVwHYMyuuzZDvQ8LRV1iBOdPuIkUAmaQ5K9ASD7VcoHexh +k8DAKf9Ln7sTRaMdvgceRNczOmJOBIEpTZkssA/jVGXZsoyTWYl1en/ZAoGBAMaW +RnNbSNprEV2b8UeAJ6i77c4SXwu1I8X2NLtiLScb1ETBjfrdHmdlJglfyd/0gmhH +p/43Ku2iGUoY5KtuOI6QmahrJYQscRQhoj252VXadG6fNWWAlpgdCm9houhHb5BF +3zge/bTr0anUe9EA7Z/ymav12rEouoNjIlhI9C5DAoGATR85a2SMt8/TB0owwdJu +62GpZNkLCmcJkXkvaecUVAOSi2hdI4o4MwMRkK35cbX5rH74y4JqCtQY5pefgP53 +sykzDAK+MyMdzxGg2764MRGegI5Yq+5jDmSquo+xF+q6srEtRk6iMG7UVwosBLmu +zuxqzySoiOfKSRKWnYe3SakCgYEAwWMkVkAmETXE4oDzFSsS8/mW2l//mPocTTK3 +JWe1CunJ6+8FYbAlZJEW2ngismp8+CoXybNVpbZ+pC7buKoMf6EHUgCNt0pEEFO0 +mCG9KSMk0XlPWXpArP9S4yaUq1itpzSz7QYZES+4rIcU0HLz9RgeWFyCTJWaFErc +7laVG9sCgYBKOtk5WlIOP4BxSd2y4cYzohgwTZIs1/2kTEn1u4eH73M1xvAlHHFB +wSF5QXgDKJ8pPAOhNWpdLO/PdtnQn91nOvTNc+ShJZzjdbneUdQVpWpoBf72uA+N +6rIVf1JBUL2p7HFHaGdUZC7KGQ+yv6ZHrE1+7202nuDvJdvGEEdFsQ== +-----END RSA PRIVATE KEY----- +", + samlServicePublicKeyEnc => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnfKBDG/K0TnGT7Xu8q1N +45sNWvIK91SqNg8nvN2uVeKoHADTcsus5Xn3id5+8Q9TuMFsW9kIEeXiaPKXQa9r +yfSNDhWDWloNkpGEeWif2BnHUu46Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnV +DNfSEASVIppEBYjDX203ypmURIzU6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+t +BlcnMrkv/40DSUkehQIl2JmlFrl2Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5N +Md0KFa6CwZUUSHJqH5GFy5Y2yl4lg8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxI +GQIDAQAB +-----END PUBLIC KEY----- +", + samlServicePublicKeySig => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtR/wgDqWB4Maho5V6Tjc +L/NbNfjgIh7GcgkrB5RZcVT1GTejJlMjUQdgBKBuZXQN+7/29P6UcGq1kYalURq6 +S8SpeJ1ofp5rBEoD/TIkvU0JOcid65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRy +BIQzB0SIxSpnrsigqNsE1E94toDMx4wovjHu/9ABAImREV7Sz83OeFF00/sghrjT +EJOD/gHf04JCn9MgNOqvSTysr9LXWg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5 +yD41mi+hT8Rh+W8Je8rsiML4VMxzsb1l9303asw6suo5bLTISKNSbu1nt1NkpNxz +ywIDAQAB +-----END PUBLIC KEY----- +", + samlSPMetaDataXML => { + "sp.com" => { + samlSPMetaDataXML => + samlSPMetaDataXML( 'sp', 'HTTP-POST' ) + }, + }, + } + } + ); +} + +sub sp { + return LLNG::Manager::Test->new( { + ini => { + logLevel => $debug, + domain => 'sp.com', + portal => 'http://auth.sp.com', + authentication => 'SAML', + userDB => 'Same', + checkUser => 1, + issuerDBSAMLActivation => 0, + restSessionServer => 1, + samlIDPMetaDataExportedAttributes => { + idp => { + mail => "0;mail;;", + uid => "1;uid", + cn => "0;cn" + } + }, + samlIDPMetaDataOptions => { + idp => { + samlIDPMetaDataOptionsEncryptionMode => 'none', + samlIDPMetaDataOptionsSSOBinding => 'post', + samlIDPMetaDataOptionsSLOBinding => 'post', + samlIDPMetaDataOptionsSignSSOMessage => 1, + samlIDPMetaDataOptionsSignSLOMessage => 1, + samlIDPMetaDataOptionsCheckSSOMessageSignature => 1, + samlIDPMetaDataOptionsCheckSLOMessageSignature => 1, + samlIDPMetaDataOptionsForceUTF8 => 1, + } + }, + samlIDPMetaDataExportedAttributes => { + idp => { + "uid" => "0;uid;;", + "cn" => "1;cn;;", + }, + }, + samlIDPMetaDataXML => { + idp => { + samlIDPMetaDataXML => + samlIDPMetaDataXML( 'idp', 'HTTP-POST' ) + } + }, + samlOrganizationDisplayName => "SP", + samlOrganizationName => "SP", + samlOrganizationURL => "http://www.sp.com", + samlServicePublicKeySig => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu4iToYAEmWQxgZDihGVz +MMql1elPn37domWcvXeU2E4yt2hh5jkQHiFjgodfOlNeRIw5QJVlUBwr+CQvbaKR +FXd7BrOhQIDC0TZPRVB0XHarUtsCuDekN4/2GKSzHsoToKUVPWq9thsuek3xkpsJ +GZNX7bglfEc9+QQpYTqN1rkdN1PVU0epNMokFFGho5pLRqLUV5+I/QXAL49jfTja +Sxsp4UndTI8/+mGSRSq+nrT2zyQRM/vkj5vR9ZVz67HO/+Wk3Mx6RAwkVcMdgMAq +Cq8odmbI0yCRZiTL9ybKWRKqWJoKJ0p5+Q2fPEBPupQZR09Jt/JPuLVSsGfCxi9N +qwIDAQAB +-----END PUBLIC KEY----- +", + samlServicePrivateKeyEnc => "-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAsRaod2RZ8hMFBl+VhsnhyPM8l/Fj1obnBxfQIaWuHFIFfXiG +e/CYHuZ5QJQLnZxHMJX6LL3Sh+Usog3p0jpijpcg0QgfBSEkfopKTgReYN8DiDIl +l0rV1XdTni7E85Nd1YyNy3ui/ZD+UShWwqu6jLVLR+QUm+/1LIKYb3OCBTvOlY7x +HoP6NSU1+Mr+YzGBUacdO2vnNxe/PQhxIeP1zO0njuqGHkwEpy8rUWRZbbDn31Tm +Kjqlhgtsz5HPhbRaYEExhyepKgBiNz+RyxtYXVhuG8OrWQDoS5gYHSjdw1CTJyix +eJwyoqA9RGYguG5nh9zndi3LWAh7Z0lx+tIz+wIDAQABAoIBAEkZrk8iiJKJ0WAx +IrsyKNbXuWKLTYgnxcRCyzKofrfID+YcU39j8JeI0fKbajQUZ7qhnlTLwtU//+2h +SqzyVu6/add/v7ZRWQw3L7cGzKK2THHzKVtLk/t7N3QroDdf1LMrQvkFP2HmcWS0 +/yN62hXtXHb/qpY4Nn+6JQyUpM5dkv8S/QjDl2NTdyWrXKzWp+4I3QLQ20f4zym+ +ir7RennziMc0HlQNcTjGAUbFULtdqEfSFWhNK7UjiRY+S0XV2xJIbGjnxUQH62fS +w1ZzYsF7sBtoSckvfL4WfGbylhOVnliU05RLU2c67PRjj1Gskoslq1Ow/3DHR7rI +BSBpV8ECgYEA1eHfcog7xQGDkW+cshJtFPFx+9MegB58gFW1rl0rn+tfbexvoSEA +7G7EOTyaU6OAI+8StiRT6AYTgEU7PMM9zDykdGIWj3h0OpHGA86xhEiiaaM2DDRv +/DEKRVlEdmRLLLY28pJVHOMYomia3mb2VKZGg2VfGtSfjg1GXD3I8OECgYEA0/X0 +U55KjZ1JQTPUgFc1WK1NxX9MaH+NcpDaolEUy3Qf3QTbfws+a9K3vwCn7EpQhrfs +I6RVUtwFdCyfl/jzBY9Gykkg03sMgW7Qw2SCCsSt05M+jDtBbNJ7esP6PAeKFvXZ +ZWhdeiAa4kM/P6gtvZXQ4tY4LkSbcd6b0SzzFFsCgYBjMsusFzuBd95JyfZnMNye +5gzzu0teKMWd0CLfqB7foQ81sH9lwCTpg8ZGtbDuMdrwz6ViDR9NceQBjhqXaAZ1 +f3rW79d+22Ms9wdcJLV4oSeSzzv2FSwLT8NvvqNeNc4YArshbnVDXKDEUrfhhueh +Ay2ZK58clpkaDVYg2hckgQKBgG3KuhtSI/YE4fwXN9yez7A2XNGPZem/IGqWo9lu +PGJCrXqT2IqPLW82gB083r6jo+CUhonTxqqb82tA7g4PUvqvQ5Dmnk1NMKYe255K +gp3HUO8GF2EWFIak5Hcr6oOLuDi6cjh3/euTk7ld8fYsTD0mzEOjiQhWW1p5X6bT +LLp/AoGAHvkxA1NM1HJ3myAREbwNXxRy/nhNt4mwMkZ6hPQsW/Eg/3r7j6MJOFrm +U8AJJjDGKe6nlXhhnMoQfJzAc0cYNgjktmJXW27fHGIwt/2QwYNFHPK3s7HTrfH6 +7T4XKT3yGeeeyC2soKJQPlGB+ETdIUnXa7eo9KVWtMTgISyx1Qk= +-----END RSA PRIVATE KEY----- +", + samlServicePrivateKeySig => "-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAu4iToYAEmWQxgZDihGVzMMql1elPn37domWcvXeU2E4yt2hh +5jkQHiFjgodfOlNeRIw5QJVlUBwr+CQvbaKRFXd7BrOhQIDC0TZPRVB0XHarUtsC +uDekN4/2GKSzHsoToKUVPWq9thsuek3xkpsJGZNX7bglfEc9+QQpYTqN1rkdN1PV +U0epNMokFFGho5pLRqLUV5+I/QXAL49jfTjaSxsp4UndTI8/+mGSRSq+nrT2zyQR +M/vkj5vR9ZVz67HO/+Wk3Mx6RAwkVcMdgMAqCq8odmbI0yCRZiTL9ybKWRKqWJoK +J0p5+Q2fPEBPupQZR09Jt/JPuLVSsGfCxi9NqwIDAQABAoIBABE0Cjb6g3F+23vD +SsRSeiqzrFrfOEqtXK+VGrfWzHS7V7Ozg6eW/H+HGJXUzUuQcklfg7EFA3JB41a0 +GxW3oA+UElkfCV/dcAG5NbRqGQKScEz9glZb5FikgDLqiPP+HabS/gvQSu71t2HI +3KxSRJdwCNTp26Z28pxxYUpmELTtxd9vlHjffit2Mnt2uc8hOtFHdNavfYwvYH7o +bmlckp7b/JVOy2Yy21O94ZWkE498jXyn71Gr+V1cnJ0RrmYbhQqIvFpFHj98Pf4O +if3c4YmBcZ4t7PUsZUYF3ooWt8k/mdigQC3D6p80OKe+wUTYKcCN0ZdFbiURv9pg +CsqLh+ECgYEA9vA+9QfzvXC7S5yXgTkuRiusPlNye/AiyA/0oGjmjFZ1YNsT7awH +6BjW6WE+rS4elKJu1GaefM/cDguH4ZmJc+eKgi4LDCqYw9rr9les3aneBc8demd3 +O/Ej1Pud1QxXArBNfBYo08vEqwST9P89clJC5090U6bGK2E0rTVu1w0CgYEAwmpG +9LbOFeGCPmwX7Avuk7tQQfRSV6q9TFZo+HxDfKYvxec846l1vBenY2rrgYhtolYJ +YS795LYgbSWRxGfgr1GuIbP5GsjHy6/1o6bS8M++GJ7KHArb0QLAYyQweqqb164A +NvHJkveueWnxzeOlD9j2fcjEnBHwTnqjG+17CZcCgYEAqMXawa4FsNxzpmIISpHC +RsNindZ60Kp3mzUMhPYtXI1a/C+/lxmU7dTMTgXgyIxU6lF6XkEk4TlPtWm8HTzK +7SS7Te4aLt6OOo5N57hUtct7q4y7IQXGQHm3e8HdRdeBQJ0u2Dhs/xSt/hTK6w/n +91Kx11Y+s02w88UkM53pe6ECgYAF/UYwVc1liSv9BlF6WSfBb1zam09KGh1405Sq +SxG9LlV8cFJE5TyWTdg/TNTyiaRvAt2JG+yAdkfrdOPXvCeE3yxRJ30+IP9evA4C +O6p19sBxe7rYQFFjUAVjSIMh1E22yEqDZtGB8JV0chob8K5uHY4CdAPylu7jTA3o +V1maAwKBgQCSGQ3yzsk4EGN2xd/JdgGDzhKyTZTQKMWYqQcsYxRAQ7Paj7u+Wkgv +dBeKcI0HwgpLy5ZohSd2erqieIsW0pEbJWCmos4IcO8tgNfEOa5WXYdyLbj5tFwt +ctu4/BJdijqfpMAtG8pv6k09gYjfASVytXmydGcs/0rVKYCRQA8Tow== +-----END RSA PRIVATE KEY----- +", + samlServicePublicKeyEnc => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsRaod2RZ8hMFBl+Vhsnh +yPM8l/Fj1obnBxfQIaWuHFIFfXiGe/CYHuZ5QJQLnZxHMJX6LL3Sh+Usog3p0jpi +jpcg0QgfBSEkfopKTgReYN8DiDIll0rV1XdTni7E85Nd1YyNy3ui/ZD+UShWwqu6 +jLVLR+QUm+/1LIKYb3OCBTvOlY7xHoP6NSU1+Mr+YzGBUacdO2vnNxe/PQhxIeP1 +zO0njuqGHkwEpy8rUWRZbbDn31TmKjqlhgtsz5HPhbRaYEExhyepKgBiNz+RyxtY +XVhuG8OrWQDoS5gYHSjdw1CTJyixeJwyoqA9RGYguG5nh9zndi3LWAh7Z0lx+tIz ++wIDAQAB +-----END PUBLIC KEY----- +", + samlSPSSODescriptorAuthnRequestsSigned => 1, + }, + } + ); +} diff --git a/lemonldap-ng-portal/t/67-CheckUser.t b/lemonldap-ng-portal/t/67-CheckUser.t index 019d2e241..3e3fb88a7 100644 --- a/lemonldap-ng-portal/t/67-CheckUser.t +++ b/lemonldap-ng-portal/t/67-CheckUser.t @@ -57,6 +57,21 @@ ok( $res->[2]->[0] =~ m%An error occurs, you're going to be redirected to%, count(1); $client->logout($id); +## Try to authenticate +ok( + $res = $client->_post( + '/', + IO::String->new('user=rtyler&password=rtyler'), + length => 27, + accept => 'text/html', + ), + 'Auth query' +); +count(1); + +$id = expectCookie($res); +expectRedirection( $res, 'http://auth.example.com/' ); + ## Try to authenticate ok( $res = $client->_post( @@ -85,7 +100,6 @@ ok( ); count(1); -# Request with bad VH my ( $host, $url, $query ) = expectForm( $res, undef, '/checkuser', 'user', 'url' ); ok( $res->[2]->[0] =~ m%%, 'Found trspan="checkUser"' ) @@ -99,8 +113,7 @@ ok( $res->[2]->[0] =~ m%dwho%, 'Found value dwho' ) or explain( $res->[2]->[0], 'Value dwho' ); count(2); -$query =~ s/user=dwho/user=rtyler/; -$query =~ s/url=/url=http%3A%2F%2Ftry.example.com/; +$query =~ s/url=/url=http%3A%2F%2Ftest1.example.com/; ok( $res = $client->_post( '/checkuser', @@ -113,6 +126,39 @@ ok( ); count(1); +( $host, $url, $query ) = + expectForm( $res, undef, '/checkuser', 'user', 'url' ); +ok( $res->[2]->[0] =~ m%%, 'Found trspan="checkUser"' ) + or explain( $res->[2]->[0], 'trspan="checkUser"' ); + +count(2); +ok( $res->[2]->[0] =~ m%Auth-User%, + 'Found Auth-User' ) + or explain( $res->[2]->[0], 'Header Key: Auth-User' ); +ok( $res->[2]->[0] =~ m%dwho%, 'Found dwho' ) + or explain( $res->[2]->[0], 'Header Value: dwho' ); +ok( $res->[2]->[0] =~ m%_whatToTrace%, + 'Found _whatToTrace' ) + or explain( $res->[2]->[0], 'Macro Key _whatToTrace' ); +ok( $res->[2]->[0] =~ m%dwho%, 'Found dwho' ) + or explain( $res->[2]->[0], 'Macro Value dwho' ); +count(3); + +$query =~ s/user=dwho/user=rtyler/; +$query =~ s/url=http%3A%2F%2Ftest1.example.com/url=http%3A%2F%2Ftry.example.com/; +ok( + $res = $client->_post( + '/checkuser', + IO::String->new($query), + cookie => "lemonldap=$id", + length => length($query), + accept => 'text/html', + ), + 'POST checkuser' +); +count(1); + +# Request with bad VH ( $host, $url, $query ) = expectForm( $res, undef, '/checkuser', 'user', 'url' ); ok( $res->[2]->[0] =~ m%%, @@ -252,7 +298,7 @@ m%
%, count(2); # Request an unknown identity -$query =~ s/user=msmith/user=davros/; +$query =~ s/user=msmith/user=dalek/; ok( $res = $client->_post( '/checkuser', diff --git a/lemonldap-ng-portal/t/lmConf-1.json b/lemonldap-ng-portal/t/lmConf-1.json index a78140ea8..f65043f34 100644 --- a/lemonldap-ng-portal/t/lmConf-1.json +++ b/lemonldap-ng-portal/t/lmConf-1.json @@ -75,7 +75,7 @@ "key": "qwertyui", "locationRules": { "auth.example.com" : { - "(?#checkUser)^/checkuser" : "$uid eq \"dwho\" or $uid eq \"msmith\"", + "(?#checkUser)^/checkuser" : "$uid eq \"dwho\" or $uid eq \"msmith\" or $uid eq \"french\"", "(?#errors)^/lmerror/": "accept", "default" : "accept" }, diff --git a/lemonldap-ng-portal/t/test-lib.pm b/lemonldap-ng-portal/t/test-lib.pm index be537f0f7..222c8dfc6 100644 --- a/lemonldap-ng-portal/t/test-lib.pm +++ b/lemonldap-ng-portal/t/test-lib.pm @@ -547,6 +547,11 @@ has ini => ( cn => 'Frédéric Accents', mail => 'fa@badwolf.org', }; + $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{davros} = { + uid => 'davros', + cn => 'Bad Guy', + mail => 'davros@badguy.org', + }; $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{russian} = { uid => 'russian', cn => 'Русский',