Allow override of username attribute for CAS apps
Global CAS options allows the admistrator to set the session attribute that gets exported to all CAS application as the main identifier (cas:user) This commit adds the ability to override this configuration for a particular CAS application. OIDC already allows this Fixes #1713
This commit is contained in:
parent
62f16721ff
commit
2f9e6aa623
|
@ -24,7 +24,7 @@ our $specialNodeHash = {
|
||||||
our $doubleHashKeys = 'issuerDBGetParameters';
|
our $doubleHashKeys = 'issuerDBGetParameters';
|
||||||
our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|c(?:as(?:StorageOption|Attribute)|ustomAddParam|ombModule)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|macro)s|o(?:idcS(?:erviceMetaDataAuthnContext|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember)|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|S(?:MTPTLSOpts|SLVarIf))';
|
our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|c(?:as(?:StorageOption|Attribute)|ustomAddParam|ombModule)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|macro)s|o(?:idcS(?:erviceMetaDataAuthnContext|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember)|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|S(?:MTPTLSOpts|SLVarIf))';
|
||||||
our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaDataNode|virtualHost)s';
|
our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaDataNode|virtualHost)s';
|
||||||
our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:Servic|Rul)e|ExportedVars)';
|
our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:UserAttribut|Servic|Rul)e|ExportedVars)';
|
||||||
our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)';
|
our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)';
|
||||||
our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|S(?:toreIDToken|ortNumber|cope)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))';
|
our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|S(?:toreIDToken|ortNumber|cope)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))';
|
||||||
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:(?:PostLogoutRedirectUri|ExtraClaim)s|I(?:DToken(?:Expiration|SignAlg)|con)|Logout(?:SessionRequired|Type|Url)|AccessTokenExpiration|R(?:edirectUris|ule)|Client(?:Secret|ID)|BypassConsent|DisplayName|UserIDAttr)|ExportedVars)';
|
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:(?:PostLogoutRedirectUri|ExtraClaim)s|I(?:DToken(?:Expiration|SignAlg)|con)|Logout(?:SessionRequired|Type|Url)|AccessTokenExpiration|R(?:edirectUris|ule)|Client(?:Secret|ID)|BypassConsent|DisplayName|UserIDAttr)|ExportedVars)';
|
||||||
|
|
|
@ -678,6 +678,9 @@ sub attributes {
|
||||||
'casAppMetaDataOptionsService' => {
|
'casAppMetaDataOptionsService' => {
|
||||||
'type' => 'url'
|
'type' => 'url'
|
||||||
},
|
},
|
||||||
|
'casAppMetaDataOptionsUserAttribute' => {
|
||||||
|
'type' => 'text'
|
||||||
|
},
|
||||||
'casAttr' => {
|
'casAttr' => {
|
||||||
'type' => 'text'
|
'type' => 'text'
|
||||||
},
|
},
|
||||||
|
|
|
@ -1824,6 +1824,10 @@ sub attributes {
|
||||||
type => 'url',
|
type => 'url',
|
||||||
documentation => 'CAS App service',
|
documentation => 'CAS App service',
|
||||||
},
|
},
|
||||||
|
casAppMetaDataOptionsUserAttribute => {
|
||||||
|
type => 'text',
|
||||||
|
documentation => 'CAS User attribute',
|
||||||
|
},
|
||||||
casAppMetaDataOptionsRule => {
|
casAppMetaDataOptionsRule => {
|
||||||
type => 'text',
|
type => 'text',
|
||||||
test => $perlExpr,
|
test => $perlExpr,
|
||||||
|
|
|
@ -252,6 +252,7 @@ sub cTrees {
|
||||||
form => 'simpleInputContainer',
|
form => 'simpleInputContainer',
|
||||||
nodes => [
|
nodes => [
|
||||||
'casAppMetaDataOptionsService',
|
'casAppMetaDataOptionsService',
|
||||||
|
'casAppMetaDataOptionsUserAttribute',
|
||||||
'casAppMetaDataOptionsRule'
|
'casAppMetaDataOptionsRule'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,11 @@ function templates(tpl,key) {
|
||||||
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsService",
|
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsService",
|
||||||
"title" : "casAppMetaDataOptionsService"
|
"title" : "casAppMetaDataOptionsService"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"get" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsUserAttribute",
|
||||||
|
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsUserAttribute",
|
||||||
|
"title" : "casAppMetaDataOptionsUserAttribute"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"get" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsRule",
|
"get" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsRule",
|
||||||
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsRule",
|
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsRule",
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -116,6 +116,7 @@
|
||||||
"casAppMetaDataOptions":"خيارات",
|
"casAppMetaDataOptions":"خيارات",
|
||||||
"casAppMetaDataOptionsService":"خدمة أل يو أر ل",
|
"casAppMetaDataOptionsService":"خدمة أل يو أر ل",
|
||||||
"casAppMetaDataOptionsRule":"القاعدة",
|
"casAppMetaDataOptionsRule":"القاعدة",
|
||||||
|
"casAppMetaDataOptionsUserAttribute":"User attribute",
|
||||||
"casAppName":"اسم التطبيق كاس",
|
"casAppName":"اسم التطبيق كاس",
|
||||||
"casAttr":"تسجيل الدخول كاس",
|
"casAttr":"تسجيل الدخول كاس",
|
||||||
"casAttributes":"السمات المصدرة لي كاس",
|
"casAttributes":"السمات المصدرة لي كاس",
|
||||||
|
@ -986,4 +987,4 @@
|
||||||
"samlRelayStateTimeout":"تناوب حالة مهلة الجلسة ",
|
"samlRelayStateTimeout":"تناوب حالة مهلة الجلسة ",
|
||||||
"samlUseQueryStringSpecific":"استخدام أسلوب query_string المعين",
|
"samlUseQueryStringSpecific":"استخدام أسلوب query_string المعين",
|
||||||
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
"casAppMetaDataOptions":"Optionen",
|
"casAppMetaDataOptions":"Optionen",
|
||||||
"casAppMetaDataOptionsService":"Service URL",
|
"casAppMetaDataOptionsService":"Service URL",
|
||||||
"casAppMetaDataOptionsRule":"Regel",
|
"casAppMetaDataOptionsRule":"Regel",
|
||||||
|
"casAppMetaDataOptionsUserAttribute":"User attribute",
|
||||||
"casAppName":"CAS App Name",
|
"casAppName":"CAS App Name",
|
||||||
"casAttr":"CAS login",
|
"casAttr":"CAS login",
|
||||||
"casAttributes":"CAS exported attributes",
|
"casAttributes":"CAS exported attributes",
|
||||||
|
@ -986,4 +987,4 @@
|
||||||
"samlRelayStateTimeout":"RelayState session timeout",
|
"samlRelayStateTimeout":"RelayState session timeout",
|
||||||
"samlUseQueryStringSpecific":"Use specific query_string method",
|
"samlUseQueryStringSpecific":"Use specific query_string method",
|
||||||
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
"casAppMetaDataOptions":"Options",
|
"casAppMetaDataOptions":"Options",
|
||||||
"casAppMetaDataOptionsService":"Service URL",
|
"casAppMetaDataOptionsService":"Service URL",
|
||||||
"casAppMetaDataOptionsRule":"Rule",
|
"casAppMetaDataOptionsRule":"Rule",
|
||||||
|
"casAppMetaDataOptionsUserAttribute":"User attribute",
|
||||||
"casAppName":"CAS App Name",
|
"casAppName":"CAS App Name",
|
||||||
"casAttr":"CAS login",
|
"casAttr":"CAS login",
|
||||||
"casAttributes":"CAS exported attributes",
|
"casAttributes":"CAS exported attributes",
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
"casAppMetaDataOptions":"Options",
|
"casAppMetaDataOptions":"Options",
|
||||||
"casAppMetaDataOptionsService":"URL du service",
|
"casAppMetaDataOptionsService":"URL du service",
|
||||||
"casAppMetaDataOptionsRule":"Règle",
|
"casAppMetaDataOptionsRule":"Règle",
|
||||||
|
"casAppMetaDataOptionsUserAttribute":"Attribut de l'identifiant",
|
||||||
"casAppName":"Nom de l'application CAS",
|
"casAppName":"Nom de l'application CAS",
|
||||||
"casAttr":"Identifiant CAS",
|
"casAttr":"Identifiant CAS",
|
||||||
"casAttributes":"Attributs CAS",
|
"casAttributes":"Attributs CAS",
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
"casAppMetaDataOptions":"Opzioni",
|
"casAppMetaDataOptions":"Opzioni",
|
||||||
"casAppMetaDataOptionsService":"URL del servizio",
|
"casAppMetaDataOptionsService":"URL del servizio",
|
||||||
"casAppMetaDataOptionsRule":"Regola",
|
"casAppMetaDataOptionsRule":"Regola",
|
||||||
|
"casAppMetaDataOptionsUserAttribute":"User attribute",
|
||||||
"casAppName":"Nome App CAS",
|
"casAppName":"Nome App CAS",
|
||||||
"casAttr":"Login CAS",
|
"casAttr":"Login CAS",
|
||||||
"casAttributes":"Attributi CAS esportati",
|
"casAttributes":"Attributi CAS esportati",
|
||||||
|
@ -986,4 +987,4 @@
|
||||||
"samlRelayStateTimeout":"Timeout di sessione di RelayState",
|
"samlRelayStateTimeout":"Timeout di sessione di RelayState",
|
||||||
"samlUseQueryStringSpecific":"Utilizza il metodo specifico query_string",
|
"samlUseQueryStringSpecific":"Utilizza il metodo specifico query_string",
|
||||||
"samlOverrideIDPEntityID":"Sostituisci l'ID entità quando agisce come IDP"
|
"samlOverrideIDPEntityID":"Sostituisci l'ID entità quando agisce come IDP"
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
"casAppMetaDataOptions":"Tùy chọn",
|
"casAppMetaDataOptions":"Tùy chọn",
|
||||||
"casAppMetaDataOptionsService":"Dịch vụ URL",
|
"casAppMetaDataOptionsService":"Dịch vụ URL",
|
||||||
"casAppMetaDataOptionsRule":"Quy tắc",
|
"casAppMetaDataOptionsRule":"Quy tắc",
|
||||||
|
"casAppMetaDataOptionsUserAttribute":"User attribute",
|
||||||
"casAppName":"Tên ứng dụng CAS",
|
"casAppName":"Tên ứng dụng CAS",
|
||||||
"casAttr":"Đăng nhập CAS ",
|
"casAttr":"Đăng nhập CAS ",
|
||||||
"casAttributes":"Thuộc tính CAS đã được xuất",
|
"casAttributes":"Thuộc tính CAS đã được xuất",
|
||||||
|
@ -986,4 +987,4 @@
|
||||||
"samlRelayStateTimeout":"Thời gian hết hạn phiên RelayState ",
|
"samlRelayStateTimeout":"Thời gian hết hạn phiên RelayState ",
|
||||||
"samlUseQueryStringSpecific":"Sử dụng phương pháp query_string cụ thể",
|
"samlUseQueryStringSpecific":"Sử dụng phương pháp query_string cụ thể",
|
||||||
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
"casAppMetaDataOptions":"选项",
|
"casAppMetaDataOptions":"选项",
|
||||||
"casAppMetaDataOptionsService":"服务 URL",
|
"casAppMetaDataOptionsService":"服务 URL",
|
||||||
"casAppMetaDataOptionsRule":"规则",
|
"casAppMetaDataOptionsRule":"规则",
|
||||||
|
"casAppMetaDataOptionsUserAttribute":"User attribute",
|
||||||
"casAppName":"CAS App 名称",
|
"casAppName":"CAS App 名称",
|
||||||
"casAttr":"CAS 登录",
|
"casAttr":"CAS 登录",
|
||||||
"casAttributes":"CAS 声明的attributes",
|
"casAttributes":"CAS 声明的attributes",
|
||||||
|
@ -986,4 +987,4 @@
|
||||||
"samlRelayStateTimeout":"RelayState session timeout",
|
"samlRelayStateTimeout":"RelayState session timeout",
|
||||||
"samlUseQueryStringSpecific":"Use specific query_string method",
|
"samlUseQueryStringSpecific":"Use specific query_string method",
|
||||||
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
||||||
}
|
}
|
||||||
|
|
|
@ -458,8 +458,17 @@ sub validate {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get username
|
# Get username
|
||||||
my $username = $localSession->data->{ $self->conf->{casAttr}
|
my $app = $casServiceSession->data->{_casApp};
|
||||||
|| $self->conf->{whatToTrace} };
|
my $username_attribute =
|
||||||
|
( $app
|
||||||
|
and $self->conf->{casAppMetaDataOptions}->{$app}
|
||||||
|
->{casAppMetaDataOptionsUserAttribute} )
|
||||||
|
? $self->conf->{casAppMetaDataOptions}->{$app}
|
||||||
|
->{casAppMetaDataOptionsUserAttribute}
|
||||||
|
: ( $self->conf->{casAttr}
|
||||||
|
|| $self->conf->{whatToTrace} );
|
||||||
|
|
||||||
|
my $username = $localSession->data->{$username_attribute};
|
||||||
|
|
||||||
$self->logger->debug("Get username $username");
|
$self->logger->debug("Get username $username");
|
||||||
|
|
||||||
|
@ -728,8 +737,16 @@ sub _validate2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get username
|
# Get username
|
||||||
my $username = $localSession->data->{ $self->conf->{casAttr}
|
my $username_attribute =
|
||||||
|| $self->conf->{whatToTrace} };
|
( $app
|
||||||
|
and $self->conf->{casAppMetaDataOptions}->{$app}
|
||||||
|
->{casAppMetaDataOptionsUserAttribute} )
|
||||||
|
? $self->conf->{casAppMetaDataOptions}->{$app}
|
||||||
|
->{casAppMetaDataOptionsUserAttribute}
|
||||||
|
: ( $self->conf->{casAttr}
|
||||||
|
|| $self->conf->{whatToTrace} );
|
||||||
|
|
||||||
|
my $username = $localSession->data->{$username_attribute};
|
||||||
|
|
||||||
$self->logger->debug("Get username $username");
|
$self->logger->debug("Get username $username");
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,322 @@
|
||||||
|
use lib 'inc';
|
||||||
|
use Test::More; # skip_all => 'CAS is in rebuild';
|
||||||
|
use strict;
|
||||||
|
use IO::String;
|
||||||
|
use LWP::UserAgent;
|
||||||
|
use LWP::Protocol::PSGI;
|
||||||
|
use MIME::Base64;
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
require 't/test-lib.pm';
|
||||||
|
}
|
||||||
|
|
||||||
|
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(@_);
|
||||||
|
ok( $req->uri =~ m#http://auth.((?:id|s)p).com([^\?]*)(?:\?(.*))?$#,
|
||||||
|
'SOAP request' );
|
||||||
|
my $host = $1;
|
||||||
|
my $url = $2;
|
||||||
|
my $query = $3;
|
||||||
|
my $res;
|
||||||
|
my $client = ( $host eq 'idp' ? $issuer : $sp );
|
||||||
|
if ( $req->method eq 'POST' ) {
|
||||||
|
my $s = $req->content;
|
||||||
|
ok(
|
||||||
|
$res = $client->_post(
|
||||||
|
$url, IO::String->new($s),
|
||||||
|
length => length($s),
|
||||||
|
query => $query,
|
||||||
|
type => 'application/xml',
|
||||||
|
),
|
||||||
|
"Execute POST request to $url"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ok(
|
||||||
|
$res = $client->_get(
|
||||||
|
$url,
|
||||||
|
type => 'application/xml',
|
||||||
|
query => $query,
|
||||||
|
),
|
||||||
|
"Execute request to $url"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expectOK($res);
|
||||||
|
ok( getHeader( $res, 'Content-Type' ) =~ m#xml#, 'Content is XML' )
|
||||||
|
or explain( $res->[1], 'Content-Type => application/xml' );
|
||||||
|
count(3);
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ok( $issuer = issuer(), 'Issuer portal' );
|
||||||
|
$handlerOR{issuer} = \@Lemonldap::NG::Handler::Main::_onReload;
|
||||||
|
count(1);
|
||||||
|
switch ('sp');
|
||||||
|
&Lemonldap::NG::Handler::Main::cfgNum( 0, 0 );
|
||||||
|
|
||||||
|
ok( $sp = sp(), 'SP portal' );
|
||||||
|
count(1);
|
||||||
|
$handlerOR{sp} = \@Lemonldap::NG::Handler::Main::_onReload;
|
||||||
|
|
||||||
|
# Simple SP access
|
||||||
|
ok(
|
||||||
|
$res = $sp->_get(
|
||||||
|
'/', accept => 'text/html',
|
||||||
|
),
|
||||||
|
'Unauth SP request'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
expectRedirection( $res,
|
||||||
|
'http://auth.idp.com/cas/login?service=http%3A%2F%2Fauth.sp.com%2F' );
|
||||||
|
|
||||||
|
# Query IdP
|
||||||
|
switch ('issuer');
|
||||||
|
ok(
|
||||||
|
$res = $issuer->_get(
|
||||||
|
'/cas/login',
|
||||||
|
query => 'service=http://auth.sp.com/',
|
||||||
|
accept => 'text/html'
|
||||||
|
),
|
||||||
|
'Query CAS server'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
expectOK($res);
|
||||||
|
my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
|
||||||
|
|
||||||
|
# Try to authenticate with an unauthorized user to IdP
|
||||||
|
my $body = $res->[2]->[0];
|
||||||
|
$body =~ s/^.*?<form.*?>//s;
|
||||||
|
$body =~ s#</form>.*$##s;
|
||||||
|
my %fields =
|
||||||
|
( $body =~ /<input type="hidden".+?name="(.+?)".+?value="(.*?)"/sg );
|
||||||
|
$fields{user} = $fields{password} = 'dwho';
|
||||||
|
use URI::Escape;
|
||||||
|
my $s = join( '&', map { "$_=" . uri_escape( $fields{$_} ) } keys %fields );
|
||||||
|
ok(
|
||||||
|
$res = $issuer->_post(
|
||||||
|
'/cas/login',
|
||||||
|
IO::String->new($s),
|
||||||
|
cookie => $pdata,
|
||||||
|
accept => 'text/html',
|
||||||
|
length => length($s),
|
||||||
|
),
|
||||||
|
'Post authentication'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
ok( $res->[2]->[0] =~ /trmsg="68"/, 'Reject reason is 68' )
|
||||||
|
or print STDERR Dumper( $res->[2]->[0] );
|
||||||
|
count(1);
|
||||||
|
|
||||||
|
# Simple SP access
|
||||||
|
ok(
|
||||||
|
$res = $sp->_get(
|
||||||
|
'/', accept => 'text/html',
|
||||||
|
),
|
||||||
|
'Unauth SP request'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
expectRedirection( $res,
|
||||||
|
'http://auth.idp.com/cas/login?service=http%3A%2F%2Fauth.sp.com%2F' );
|
||||||
|
|
||||||
|
# Query IdP
|
||||||
|
switch ('issuer');
|
||||||
|
ok(
|
||||||
|
$res = $issuer->_get(
|
||||||
|
'/cas/login',
|
||||||
|
query => 'service=http://auth.sp.com/',
|
||||||
|
accept => 'text/html'
|
||||||
|
),
|
||||||
|
'Query CAS server'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
expectOK($res);
|
||||||
|
$pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
|
||||||
|
|
||||||
|
# Try to authenticate with an authorized to IdP
|
||||||
|
$body = $res->[2]->[0];
|
||||||
|
$body =~ s/^.*?<form.*?>//s;
|
||||||
|
$body =~ s#</form>.*$##s;
|
||||||
|
%fields = ( $body =~ /<input type="hidden".+?name="(.+?)".+?value="(.*?)"/sg );
|
||||||
|
$fields{user} = $fields{password} = 'french';
|
||||||
|
use URI::Escape;
|
||||||
|
$s = join( '&', map { "$_=" . uri_escape( $fields{$_} ) } keys %fields );
|
||||||
|
ok(
|
||||||
|
$res = $issuer->_post(
|
||||||
|
'/cas/login',
|
||||||
|
IO::String->new($s),
|
||||||
|
cookie => $pdata,
|
||||||
|
accept => 'text/html',
|
||||||
|
length => length($s),
|
||||||
|
),
|
||||||
|
'Post authentication'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
my ($query) =
|
||||||
|
expectRedirection( $res, qr#^http://auth.sp.com/\?(ticket=[^&]+)$# );
|
||||||
|
my $idpId = expectCookie($res);
|
||||||
|
|
||||||
|
# Back to SP
|
||||||
|
switch ('sp');
|
||||||
|
ok( $res = $sp->_get( '/', query => $query, accept => 'text/html' ),
|
||||||
|
'Query SP with ticket' );
|
||||||
|
count(1);
|
||||||
|
my $spId = expectCookie($res);
|
||||||
|
|
||||||
|
# Test authentication, with mail as the main identity attribute
|
||||||
|
ok( $res = $sp->_get( '/', cookie => "lemonldap=$spId" ), 'Get / on SP' );
|
||||||
|
count(1);
|
||||||
|
expectOK($res);
|
||||||
|
expectAuthenticatedAs( $res, 'fa@badwolf.org' );
|
||||||
|
|
||||||
|
# Test attributes
|
||||||
|
ok( $res = $sp->_get("/sessions/global/$spId"), 'Get UTF-8' );
|
||||||
|
expectOK($res);
|
||||||
|
ok( $res = eval { JSON::from_json( $res->[2]->[0] ) }, ' GET JSON' )
|
||||||
|
or print STDERR $@;
|
||||||
|
ok( $res->{cn} eq 'Frédéric Accents', 'UTF-8 values' )
|
||||||
|
or explain( $res, 'cn => Frédéric Accents' );
|
||||||
|
count(3);
|
||||||
|
|
||||||
|
# Logout initiated by SP
|
||||||
|
ok(
|
||||||
|
$res = $sp->_get(
|
||||||
|
'/',
|
||||||
|
query => 'logout',
|
||||||
|
cookie => "lemonldap=$spId",
|
||||||
|
accept => 'text/html'
|
||||||
|
),
|
||||||
|
'Query SP for logout'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
expectOK($res);
|
||||||
|
ok(
|
||||||
|
$res->[2]->[0] =~ m#iframe src="http://auth.idp.com(/cas/logout)\?(.+?)"#s,
|
||||||
|
'Found iframe'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
|
||||||
|
# Query IdP with iframe src
|
||||||
|
my $url = $1;
|
||||||
|
$query = $2;
|
||||||
|
ok( getHeader( $res, 'Content-Security-Policy' ) =~ /child-src auth.idp.com/,
|
||||||
|
'Frame is authorizated' )
|
||||||
|
or
|
||||||
|
explain( $res->[1], 'Content-Security-Policy => ...child-src auth.idp.com' );
|
||||||
|
count(1);
|
||||||
|
|
||||||
|
switch ('issuer');
|
||||||
|
ok(
|
||||||
|
$res = $issuer->_get(
|
||||||
|
$url,
|
||||||
|
query => $query,
|
||||||
|
accept => 'text/html',
|
||||||
|
cookie => "lemonldap=$idpId"
|
||||||
|
),
|
||||||
|
'Get iframe from IdP'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
expectRedirection( $res, 'http://auth.sp.com/?logout' );
|
||||||
|
my $h = getHeader( $res, 'Content-Security-Policy' );
|
||||||
|
ok( ( not $h or $h !~ /frame-ancestors/ ), ' Frame can be embedded' )
|
||||||
|
or explain( $res->[1],
|
||||||
|
'Content-Security-Policy does not contain a frame-ancestors' );
|
||||||
|
count(1);
|
||||||
|
|
||||||
|
# Verify that user has been disconnected
|
||||||
|
ok( $res = $issuer->_get( '/', cookie => "lemonldap=$idpId" ), 'Query IdP' );
|
||||||
|
count(1);
|
||||||
|
expectReject($res);
|
||||||
|
|
||||||
|
switch ('sp');
|
||||||
|
ok(
|
||||||
|
$res =
|
||||||
|
$sp->_get( '/', accept => 'text/html', cookie => "lemonldap=$idpId" ),
|
||||||
|
'Query IdP'
|
||||||
|
);
|
||||||
|
count(1);
|
||||||
|
expectRedirection( $res,
|
||||||
|
'http://auth.idp.com/cas/login?service=http%3A%2F%2Fauth.sp.com%2F' );
|
||||||
|
|
||||||
|
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,
|
||||||
|
templatesDir => 'site/htdocs/static',
|
||||||
|
domain => 'idp.com',
|
||||||
|
portal => 'http://auth.idp.com',
|
||||||
|
authentication => 'Demo',
|
||||||
|
userDB => 'Same',
|
||||||
|
issuerDBCASActivation => 1,
|
||||||
|
issuerDBCASRule => '$uid eq "french"',
|
||||||
|
casAttr => 'uid',
|
||||||
|
casAccessControlPolicy => 'error',
|
||||||
|
multiValuesSeparator => ';',
|
||||||
|
casAppMetaDataExportedVars => {
|
||||||
|
sp => {
|
||||||
|
cn => 'cn',
|
||||||
|
mail => 'mail',
|
||||||
|
uid => 'uid',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
casAppMetaDataOptions => {
|
||||||
|
sp => {
|
||||||
|
casAppMetaDataOptionsService => 'http://auth.sp.com',
|
||||||
|
casAppMetaDataOptionsUserAttribute => 'mail',
|
||||||
|
},
|
||||||
|
sp2 => {
|
||||||
|
casAppMetaDataOptionsService => 'http://auth.sp2.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sp {
|
||||||
|
return LLNG::Manager::Test->new( {
|
||||||
|
ini => {
|
||||||
|
logLevel => $debug,
|
||||||
|
domain => 'sp.com',
|
||||||
|
portal => 'http://auth.sp.com',
|
||||||
|
authentication => 'CAS',
|
||||||
|
userDB => 'CAS',
|
||||||
|
restSessionServer => 1,
|
||||||
|
issuerDBCASActivation => 0,
|
||||||
|
multiValuesSeparator => ';',
|
||||||
|
exportedVars => {
|
||||||
|
cn => 'cn',
|
||||||
|
},
|
||||||
|
casSrvMetaDataExportedVars => {
|
||||||
|
idp => {
|
||||||
|
cn => 'cn',
|
||||||
|
mail => 'mail',
|
||||||
|
uid => 'uid',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
casSrvMetaDataOptions => {
|
||||||
|
idp => {
|
||||||
|
casSrvMetaDataOptionsUrl => 'http://auth.idp.com/cas',
|
||||||
|
casSrvMetaDataOptionsGateway => 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user