New Issuer::CAS (#1183)

This commit is contained in:
Xavier Guimard 2017-04-13 19:17:29 +00:00
parent d1d57fae22
commit b83374b274
11 changed files with 84 additions and 35 deletions

View File

@ -24,7 +24,7 @@ our $specialNodeHash = {
our $doubleHashKeys = 'issuerDBGetParameters'; our $doubleHashKeys = 'issuerDBGetParameters';
our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|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)|re(?:moteGlobalStorageOption|loadUrl)|macro)s|o(?:idcS(?:erviceMetaDataAuthnContext|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember)|a(?:uthChoiceModules|pplicationList)|S(?:MTPTLSOpts|SLVarIf))'; our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|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)|re(?:moteGlobalStorageOption|loadUrl)|macro)s|o(?:idcS(?:erviceMetaDataAuthnContext|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember)|a(?:uthChoiceModules|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(?:OptionsService|ExportedVars)'; our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:Servic|Rul)e|ExportedVars)';
our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|Gateway|Renew|Icon|Url)|ExportedVars)'; our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|Gateway|Renew|Icon|Url)|ExportedVars)';
our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|S(?:toreIDToken|cope)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))'; our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|S(?:toreIDToken|cope)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))';
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:I(?:DToken(?:Expiration|SignAlg)|con)|Logout(?:SessionRequired|Type|Url)|AccessTokenExpiration|R(?:edirectUris|ule)|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|ExportedVars)'; our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:I(?:DToken(?:Expiration|SignAlg)|con)|Logout(?:SessionRequired|Type|Url)|AccessTokenExpiration|R(?:edirectUris|ule)|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|ExportedVars)';
@ -67,6 +67,5 @@ our $issuerParameters = {
}; };
our $samlServiceParameters = [qw(samlEntityID samlServicePrivateKeySig samlServicePrivateKeySigPwd samlServicePublicKeySig samlServicePrivateKeyEnc samlServicePrivateKeyEncPwd samlServicePublicKeyEnc samlServiceUseCertificateInResponse samlNameIDFormatMapEmail samlNameIDFormatMapX509 samlNameIDFormatMapWindows samlNameIDFormatMapKerberos samlAuthnContextMapPassword samlAuthnContextMapPasswordProtectedTransport samlAuthnContextMapTLSClient samlAuthnContextMapKerberos samlOrganizationDisplayName samlOrganizationName samlOrganizationURL samlSPSSODescriptorAuthnRequestsSigned samlSPSSODescriptorWantAssertionsSigned samlSPSSODescriptorSingleLogoutServiceHTTPRedirect samlSPSSODescriptorSingleLogoutServiceHTTPPost samlSPSSODescriptorSingleLogoutServiceSOAP samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact samlSPSSODescriptorAssertionConsumerServiceHTTPPost samlSPSSODescriptorArtifactResolutionServiceArtifact samlIDPSSODescriptorWantAuthnRequestsSigned samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect samlIDPSSODescriptorSingleSignOnServiceHTTPPost samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect samlIDPSSODescriptorSingleLogoutServiceHTTPPost samlIDPSSODescriptorSingleLogoutServiceSOAP samlIDPSSODescriptorArtifactResolutionServiceArtifact samlAttributeAuthorityDescriptorAttributeServiceSOAP samlIdPResolveCookie samlMetadataForceUTF8 samlStorage samlStorageOptions samlRelayStateTimeout samlUseQueryStringSpecific samlCommonDomainCookieActivation samlCommonDomainCookieDomain samlCommonDomainCookieReader samlCommonDomainCookieWriter)]; our $samlServiceParameters = [qw(samlEntityID samlServicePrivateKeySig samlServicePrivateKeySigPwd samlServicePublicKeySig samlServicePrivateKeyEnc samlServicePrivateKeyEncPwd samlServicePublicKeyEnc samlServiceUseCertificateInResponse samlNameIDFormatMapEmail samlNameIDFormatMapX509 samlNameIDFormatMapWindows samlNameIDFormatMapKerberos samlAuthnContextMapPassword samlAuthnContextMapPasswordProtectedTransport samlAuthnContextMapTLSClient samlAuthnContextMapKerberos samlOrganizationDisplayName samlOrganizationName samlOrganizationURL samlSPSSODescriptorAuthnRequestsSigned samlSPSSODescriptorWantAssertionsSigned samlSPSSODescriptorSingleLogoutServiceHTTPRedirect samlSPSSODescriptorSingleLogoutServiceHTTPPost samlSPSSODescriptorSingleLogoutServiceSOAP samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact samlSPSSODescriptorAssertionConsumerServiceHTTPPost samlSPSSODescriptorArtifactResolutionServiceArtifact samlIDPSSODescriptorWantAuthnRequestsSigned samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect samlIDPSSODescriptorSingleSignOnServiceHTTPPost samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect samlIDPSSODescriptorSingleLogoutServiceHTTPPost samlIDPSSODescriptorSingleLogoutServiceSOAP samlIDPSSODescriptorArtifactResolutionServiceArtifact samlAttributeAuthorityDescriptorAttributeServiceSOAP samlIdPResolveCookie samlMetadataForceUTF8 samlStorage samlStorageOptions samlRelayStateTimeout samlUseQueryStringSpecific samlCommonDomainCookieActivation samlCommonDomainCookieDomain samlCommonDomainCookieReader samlCommonDomainCookieWriter)];
our $oidcServiceParameters = [qw(oidcServiceMetaDataIssuer oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI oidcServiceMetaDataEndSessionURI oidcServiceMetaDataCheckSessionURI oidcServiceMetaDataFrontChannelURI oidcServiceMetaDataBackChannelURI oidcServiceMetaDataAuthnContext oidcServicePrivateKeySig oidcServicePublicKeySig oidcServiceKeyIdSig oidcServiceAllowDynamicRegistration oidcServiceAllowAuthorizationCodeFlow oidcServiceAllowImplicitFlow oidcServiceAllowHybridFlow oidcStorage oidcStorageOptions)]; our $oidcServiceParameters = [qw(oidcServiceMetaDataIssuer oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI oidcServiceMetaDataEndSessionURI oidcServiceMetaDataCheckSessionURI oidcServiceMetaDataFrontChannelURI oidcServiceMetaDataBackChannelURI oidcServiceMetaDataAuthnContext oidcServicePrivateKeySig oidcServicePublicKeySig oidcServiceKeyIdSig oidcServiceAllowDynamicRegistration oidcServiceAllowAuthorizationCodeFlow oidcServiceAllowImplicitFlow oidcServiceAllowHybridFlow oidcStorage oidcStorageOptions)];
our $casServiceParameters = [qw()];
1; 1;

View File

@ -640,6 +640,25 @@ sub attributes {
'casAppMetaDataOptions' => { 'casAppMetaDataOptions' => {
'type' => 'subContainer' 'type' => 'subContainer'
}, },
'casAppMetaDataOptionsRule' => {
'test' => sub {
my ( $val, $conf ) = @_;
my $s = '';
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55";
}
eval "$s $val";
my $err = join(
'',
grep( { $_ =~ /Undefined subroutine/ ? () : $_; }
split( /\n/, $@, 0 ) )
);
return $err ? ( 1, "__badExpression__: $err" ) : 1;
},
'type' => 'text'
},
'casAppMetaDataOptionsService' => { 'casAppMetaDataOptionsService' => {
'type' => 'url' 'type' => 'url'
}, },

View File

@ -214,7 +214,7 @@ EOF
} }
print F "$tmp};\n"; print F "$tmp};\n";
} }
foreach (qw(samlServiceParameters oidcServiceParameters casServiceParameters)) { foreach (qw(samlServiceParameters oidcServiceParameters)) {
no strict 'refs'; no strict 'refs';
$tmp = "our \$$_ = [qw(" . join( ' ', @$$_ ) . ")];\n"; $tmp = "our \$$_ = [qw(" . join( ' ', @$$_ ) . ")];\n";
print F "$tmp"; print F "$tmp";

View File

@ -1237,16 +1237,21 @@ sub attributes {
}, },
# Partners # Partners
casAppMetaDataOptions => { type => 'subContainer', }, casAppMetaDataOptions => { type => 'subContainer', },
casAppMetaDataExportedVars => { casAppMetaDataExportedVars => {
type => 'keyTextContainer', type => 'keyTextContainer',
default => { cn => 'cn', mail => 'mail', uid => 'uid', }, default => { cn => 'cn', mail => 'mail', uid => 'uid', },
documentation => 'CAS exported variables', documentation => 'CAS exported variables',
}, },
casAppMetaDataOptionsService => { casAppMetaDataOptionsService => {
type => 'url', type => 'url',
documentation => 'CAS App service', documentation => 'CAS App service',
}, },
casAppMetaDataOptionsRule => {
type => 'text',
test => $perlExpr,
documentation => 'CAS App rule',
},
# Fake attribute: used by manager REST API to agglomerate all nodes # Fake attribute: used by manager REST API to agglomerate all nodes
# related to a SAML SP partner # related to a SAML SP partner
@ -2083,7 +2088,7 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
default => { cn => 'cn', mail => 'mail', uid => 'uid', }, default => { cn => 'cn', mail => 'mail', uid => 'uid', },
documentation => 'CAS exported variables', documentation => 'CAS exported variables',
}, },
casSrvMetaDataOptions => { type => 'subContainer', }, casSrvMetaDataOptions => { type => 'subContainer', },
casSrvMetaDataOptionsGateway => { type => 'bool', }, casSrvMetaDataOptionsGateway => { type => 'bool', },
casSrvMetaDataOptionsProxiedServices => { casSrvMetaDataOptionsProxiedServices => {
type => 'keyTextContainer', type => 'keyTextContainer',
@ -2097,11 +2102,11 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
msgFail => '__badUrl__', msgFail => '__badUrl__',
}, },
casSrvMetaDataOptionsDisplayName => { casSrvMetaDataOptionsDisplayName => {
type => 'text', type => 'text',
documentation => 'Name to display for CAS server', documentation => 'Name to display for CAS server',
}, },
casSrvMetaDataOptionsIcon => { casSrvMetaDataOptionsIcon => {
type => 'text', type => 'text',
documentation => 'Path of CAS Server Icon', documentation => 'Path of CAS Server Icon',
}, },

View File

@ -231,7 +231,10 @@ sub cTrees {
casAppMetaDataNode => [ casAppMetaDataNode => [
{ {
title => 'casAppMetaDataOptions', title => 'casAppMetaDataOptions',
nodes => ['casAppMetaDataOptionsService'] nodes => [
'casAppMetaDataOptionsService',
'casAppMetaDataOptionsRule'
]
}, },
'casAppMetaDataExportedVars', 'casAppMetaDataExportedVars',
], ],

View File

@ -16,6 +16,11 @@ function templates(tpl,key) {
"get" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsService", "get" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsService",
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsService", "id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsService",
"title" : "casAppMetaDataOptionsService" "title" : "casAppMetaDataOptionsService"
},
{
"get" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsRule",
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsRule",
"title" : "casAppMetaDataOptionsRule"
} }
], ],
"id" : "casAppMetaDataOptions", "id" : "casAppMetaDataOptions",

File diff suppressed because one or more lines are too long

View File

@ -108,6 +108,7 @@
"casAppMetaDataNodes": "CAS Applications", "casAppMetaDataNodes": "CAS Applications",
"casAppMetaDataOptions": "Options", "casAppMetaDataOptions": "Options",
"casAppMetaDataOptionsService": "Service URL", "casAppMetaDataOptionsService": "Service URL",
"casAppMetaDataOptionsRule": "Rule",
"casAppName": "CAS App Name", "casAppName": "CAS App Name",
"casAttr": "CAS login", "casAttr": "CAS login",
"casAttributes": "CAS exported attributes", "casAttributes": "CAS exported attributes",

View File

@ -108,6 +108,7 @@
"casAppMetaDataNodes": "Applications CAS", "casAppMetaDataNodes": "Applications CAS",
"casAppMetaDataOptions": "Options", "casAppMetaDataOptions": "Options",
"casAppMetaDataOptionsService": "URL du service", "casAppMetaDataOptionsService": "URL du service",
"casAppMetaDataOptionsRule": "Règle",
"casAppName": "Nom de l'application CAS", "casAppName": "Nom de l'application CAS",
"casAttr": "Identifiant CAS", "casAttr": "Identifiant CAS",
"casAttributes": "Attributs CAS", "casAttributes": "Attributs CAS",

View File

@ -27,6 +27,7 @@ sub init {
# Launch parents initialization subroutines, then launch IdP en SP lists # Launch parents initialization subroutines, then launch IdP en SP lists
my $res = $self->Lemonldap::NG::Portal::Main::Issuer::init(); my $res = $self->Lemonldap::NG::Portal::Main::Issuer::init();
return 0 unless($self->loadApp);
$self->addUnauthRoute( $self->addUnauthRoute(
( $self->path ) => { ( $self->path ) => {
serviceValidate => 'serviceValidate', serviceValidate => 'serviceValidate',
@ -108,35 +109,37 @@ sub run {
"CAS access control requested on service $service"); "CAS access control requested on service $service");
## HERE ## HERE
unless ( $service =~ m#^https?://([^/]+)(/.*)?$# ) { unless ( $service =~ m#^(https?://[^/]+)(/.*)?$# ) {
$self->logger->error("Bad service $service"); $self->logger->error("Bad service $service");
return PE_ERROR; return PE_ERROR;
} }
my ( $host, $uri ) = ( $1, $2 ); my ( $host, $uri ) = ( $1, $2 );
if ( my $app;
$self->p->HANDLER->grant( unless($app = $self->casAppList->{$host} ) {
$req, $req->sessionInfo, $1, undef, $2 $self->userLogger->error('CAS service not configured');
) return PE_CAS_SERVICE_NOT_ALLOWED;
)
{
$self->logger->debug("CAS service $service access allowed");
} }
if ( my $rule = $self->appRules->{$app} ) {
else { if($rule->($req, $req->sessionInfo ) ) {
$self->userLogger->error( $self->logger->debug("CAS service $service access allowed");
"CAS service $service access not allowed");
if ( $casAccessControlPolicy =~ /^(error)$/i ) {
$self->logger->debug(
"Return error instead of redirecting user on CAS service"
);
return PE_CAS_SERVICE_NOT_ALLOWED;
} }
else { else {
$self->logger->debug( $self->userLogger->error(
"Redirect user on CAS service with a fake ticket"); "CAS service $service access not allowed");
$casServiceTicket = "ST-F4K3T1CK3T";
if ( $casAccessControlPolicy =~ /^(error)$/i ) {
$self->logger->debug(
"Return error instead of redirecting user on CAS service"
);
return PE_CAS_SERVICE_NOT_ALLOWED;
}
else {
$self->logger->debug(
"Redirect user on CAS service with a fake ticket");
$casServiceTicket = "ST-F4K3T1CK3T";
}
} }
} }
} }

View File

@ -25,6 +25,7 @@ has ua => (
has casSrvList => ( is => 'rw', default => sub { {} }, ); has casSrvList => ( is => 'rw', default => sub { {} }, );
has casAppList => ( is => 'rw', default => sub { {} }, ); has casAppList => ( is => 'rw', default => sub { {} }, );
has appRules => ( is => 'rw', default => sub { {} }, );
# RUNNING METHODS # RUNNING METHODS
@ -50,9 +51,21 @@ sub loadApp {
} }
foreach ( keys %{ $self->conf->{casAppMetaDataOptions} } ) { foreach ( keys %{ $self->conf->{casAppMetaDataOptions} } ) {
my $tmp = my $tmp =
$self->conf->{casAppMetaDataOptions}->{casAppMetaDataOptionsService}; $self->conf->{casAppMetaDataOptions}->{$_}
->{casAppMetaDataOptionsService};
$tmp =~ s#^(https?://[^/]+).*$#$1#; $tmp =~ s#^(https?://[^/]+).*$#$1#;
$self->casAppList->{$tmp} = $_; $self->casAppList->{$tmp} = $_;
my $rule = $self->conf->{casAppMetaDataOptions}->{$_}
->{casAppMetaDataOptionsRule};
if ( length $rule ) {
$rule = $self->p->HANDLER->substitute($rule);
unless ( $rule = $self->p->HANDLER->buildSub($rule) ) {
$self->error( 'OIDC RP rule error: '
. $self->p->HANDLER->tsv->{jail}->error );
return 0;
}
$self->appRules->{$_} = $rule;
}
} }
return 1; return 1;
} }
@ -315,7 +328,7 @@ sub validateST {
my $proxied = $srvConf->{casSrvMetaDataOptionsProxiedServices} || {}; my $proxied = $srvConf->{casSrvMetaDataOptionsProxiedServices} || {};
my $proxy_url; my $proxy_url;
if(%$proxied) { if (%$proxied) {
$proxy_url = $self->p->fullUrl($req) . '?casProxy=1'; $proxy_url = $self->p->fullUrl($req) . '?casProxy=1';
if ( $self->conf->{authChoiceParam} if ( $self->conf->{authChoiceParam}
and my $tmp = $req->param( $self->conf->{authChoiceParam} ) ) and my $tmp = $req->param( $self->conf->{authChoiceParam} ) )
@ -327,7 +340,7 @@ sub validateST {
$req->datas->{casProxyUrl} = $proxy_url; $req->datas->{casProxyUrl} = $proxy_url;
$serviceValidateUrl .= "&pgtUrl=" . uri_escape( $proxy_url ); $serviceValidateUrl .= "&pgtUrl=" . uri_escape($proxy_url);
} }
my $response = $self->ua->get($serviceValidateUrl); my $response = $self->ua->get($serviceValidateUrl);
@ -348,7 +361,7 @@ sub validateST {
} }
# Get proxy data and store pgtId # Get proxy data and store pgtId
if ( $proxy_url ) { if ($proxy_url) {
my $pgtIou = my $pgtIou =
$xml->{'cas:authenticationSuccess'}->{'cas:proxyGrantingTicket'}; $xml->{'cas:authenticationSuccess'}->{'cas:proxyGrantingTicket'};