Commit 2efb4455 authored by Christophe Maudoux's avatar Christophe Maudoux 🐛

Merge branch 'v2.0'

parents 9231711a c01c26af
Pipeline #7009 canceled with stage
......@@ -129,7 +129,7 @@
.\" ========================================================================
.\"
.IX Title "llng-fastcgi-server 8"
.TH llng-fastcgi-server 8 "2019-09-24" "perl v5.28.1" "User Contributed Perl Documentation"
.TH llng-fastcgi-server 8 "2019-10-30" "perl v5.26.1" "User Contributed Perl Documentation"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
......
......@@ -305,9 +305,10 @@ languages = en, fr, vi, it, ar, de, fi
; Read Lemonldap::NG::Portal::Main::Plugin(3pm) man page.
;customPlugins = My::Package1, My::Package2
; To avoid bad/expired OTT if authssl and auth are served by different Load Balancers
; you can override OTT configuration to store Upgrade OTT into global storage
; To avoid bad/expired OTT if "authssl" and "auth" are served by different Load Balancers
; you can override OTT configuration to store Upgrade or Issuer OTT into global storage
;forceGlobalStorageUpgradeOTT = 1
;forceGlobalStorageIssuerOTT = 1
[handler]
......
......@@ -260,8 +260,7 @@ sub defaultValues {
'samlAuthnContextMapPassword' => 2,
'samlAuthnContextMapPasswordProtectedTransport' => 3,
'samlAuthnContextMapTLSClient' => 5,
'samlEntityID' => '#PORTAL#/saml/metadata',
'samlIdPResolveCookie' => 'lemonldapidp',
'samlEntityID' => '#PORTAL#/saml/metadata',
'samlIDPSSODescriptorArtifactResolutionServiceArtifact' =>
'1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/artifact',
'samlIDPSSODescriptorSingleLogoutServiceHTTPPost' =>
......
......@@ -195,9 +195,11 @@ sub virtualHosts {
type => 'keyText',
};
# If rule contains a comment, split it
# If rule contains a comment or an AuthLevel, split them
if ( $query eq 'locationRules' ) {
$res->{comment} = '';
$res->{level} = '';
$res->{level} = $1 if ( $r =~ s/\(\?#AuthnLevel=(-?\d+)\)// );
if ( $r =~ s/\(\?#(.*?)\)// ) {
$res->{title} = $res->{comment} = $1;
}
......
......@@ -67,7 +67,7 @@ our $issuerParameters = {
issuerDBSAML => [qw(issuerDBSAMLActivation issuerDBSAMLPath issuerDBSAMLRule)],
issuerOptions => [qw(issuersTimeout)],
};
our $samlServiceParameters = [qw(samlEntityID samlServicePrivateKeySig samlServicePrivateKeySigPwd samlServicePublicKeySig samlServicePrivateKeyEnc samlServicePrivateKeyEncPwd samlServicePublicKeyEnc samlServiceUseCertificateInResponse samlServiceSignatureMethod 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 samlDiscoveryProtocolActivation samlDiscoveryProtocolURL samlDiscoveryProtocolPolicy samlDiscoveryProtocolIsPassive samlOverrideIDPEntityID)];
our $samlServiceParameters = [qw(samlEntityID samlServicePrivateKeySig samlServicePrivateKeySigPwd samlServicePublicKeySig samlServicePrivateKeyEnc samlServicePrivateKeyEncPwd samlServicePublicKeyEnc samlServiceUseCertificateInResponse samlServiceSignatureMethod 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 samlMetadataForceUTF8 samlStorage samlStorageOptions samlRelayStateTimeout samlUseQueryStringSpecific samlCommonDomainCookieActivation samlCommonDomainCookieDomain samlCommonDomainCookieReader samlCommonDomainCookieWriter samlDiscoveryProtocolActivation samlDiscoveryProtocolURL samlDiscoveryProtocolPolicy samlDiscoveryProtocolIsPassive samlOverrideIDPEntityID)];
our $oidcServiceParameters = [qw(oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI oidcServiceMetaDataIntrospectionURI oidcServiceMetaDataEndSessionURI oidcServiceMetaDataCheckSessionURI oidcServiceMetaDataFrontChannelURI oidcServiceMetaDataBackChannelURI oidcServiceMetaDataAuthnContext oidcServicePrivateKeySig oidcServicePublicKeySig oidcServiceKeyIdSig oidcServiceAllowDynamicRegistration oidcServiceAllowAuthorizationCodeFlow oidcServiceAllowImplicitFlow oidcServiceAllowHybridFlow oidcStorage oidcStorageOptions)];
1;
......@@ -43,11 +43,11 @@ sub run {
# Catch Secure Token parameters
my $localConfig = $class->localConfig;
my $secureTokenMemcachedServers =
our $secureTokenMemcachedServers =
$localConfig->{secureTokenMemcachedServers} || ['127.0.0.1:11211'];
my $secureTokenExpiration = $localConfig->{secureTokenExpiration} || 60;
my $secureTokenAttribute = $localConfig->{secureTokenAttribute} || 'uid';
my $secureTokenUrls = $localConfig->{'secureTokenUrls'} || ['.*'];
our $secureTokenUrls = $localConfig->{'secureTokenUrls'} || ['.*'];
my $secureTokenHeader = $localConfig->{secureTokenHeader} || 'Auth-Token';
my $secureTokenAllowOnError = $localConfig->{'secureTokenAllowOnError'}
// 1;
......
......@@ -281,6 +281,7 @@ sub locationRulesInit {
$class->tsv->{locationProtection}->{$vhost} = [];
$class->tsv->{locationRegexp}->{$vhost} = [];
$class->tsv->{locationConditionText}->{$vhost} = [];
$class->tsv->{locationAuthnLevel}->{$vhost} = [];
foreach my $url ( sort keys %{$rules} ) {
my ( $cond, $prot ) = $class->conditionSub( $rules->{$url} );
......@@ -300,10 +301,14 @@ sub locationRulesInit {
push @{ $class->tsv->{locationCondition}->{$vhost} }, $cond;
push @{ $class->tsv->{locationProtection}->{$vhost} }, $prot;
push @{ $class->tsv->{locationRegexp}->{$vhost} }, qr/$url/;
push @{ $class->tsv->{locationAuthnLevel}->{$vhost} },
$url =~ /\(\?#AuthnLevel=(-?\d+)\)/
? $1
: undef;
push @{ $class->tsv->{locationConditionText}->{$vhost} },
$url =~ /^\(\?#(.*?)\)/ ? $1
: $url =~ /^(.*?)##(.+)$/ ? $2
: $url;
: $url;
$class->tsv->{locationCount}->{$vhost}++;
}
}
......@@ -451,6 +456,7 @@ sub postUrlInit {
# @return array (ref(sub), int)
sub conditionSub {
my ( $class, $cond ) = @_;
$cond =~ s/\(\?#(\d+)\)$//;
my ( $OK, $NOK ) = ( sub { 1 }, sub { 0 } );
# Simple cases : accept and deny
......
......@@ -267,10 +267,31 @@ sub checkMaintenanceMode {
# @return True if the user is granted to access to the current URL
sub grant {
my ( $class, $req, $session, $uri, $cond, $vhost ) = @_;
my $level;
return $cond->( $req, $session ) if ($cond);
$vhost ||= $class->resolveAlias($req);
if ( my $level = $class->tsv->{authnLevel}->{$vhost} ) {
# Using URL authentification level if exists
for (
my $i = 0 ;
$i < ( $class->tsv->{locationCount}->{$vhost} || 0 ) ;
$i++
)
{
if ( $uri =~ $class->tsv->{locationRegexp}->{$vhost}->[$i] ) {
$level = $class->tsv->{locationAuthnLevel}->{$vhost}->[$i];
last;
}
}
$level
? $class->logger->debug(
'Found AuthnLevel=' . $level . ' for "' . "$vhost$uri" . '"' )
: $class->logger->debug("No URL authentication level found...");
# Using VH authentification level if exists
if ( $level ||= $class->tsv->{authnLevel}->{$vhost} ) {
if ( $session->{authenticationLevel} < $level ) {
$class->logger->debug(
"User authentication level = $session->{authenticationLevel}");
......
......@@ -10,6 +10,7 @@ init('Lemonldap::NG::Handler::PSGI');
my $res;
# Unauthentified query
# --------------------
ok( $res = $client->_get('/'), 'Unauthentified query' );
ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' );
ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 );
......@@ -24,17 +25,14 @@ ok(
'Location => http://auth.example.com/?url='
. encode_base64( 'http://test1.example.com/', '' )
);
count(4);
# Authentified queries
# --------------------
# Authorized query
ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ),
'Authentified query' );
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 );
count(2);
ok( $res = $client->_get( '/user_dwho/', undef, undef, "lemonldap=$sessionId" ),
......@@ -47,7 +45,12 @@ count(2);
ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ),
'Denied query' );
ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 );
count(2);
# Required AuthnLevel = 1
ok( $res = $client->_get( '/AuthWeak', undef, undef, "lemonldap=$sessionId" ),
'Weak Authentified query' );
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 );
count(2);
ok( $res = $client->_get( '/user_rtyler/', undef, undef, "lemonldap=$sessionId" ),
......@@ -56,6 +59,25 @@ ok( $res->[0] == 403, 'Code is 403' ) or explain( $res, 403 );
count(2);
# Required AuthnLevel = 5
ok(
$res = $client->_get( '/AuthStrong', undef, undef, "lemonldap=$sessionId" ),
'Strong Authentified query'
);
ok( $res->[0] == 302, 'Code is 302' ) or explain( $res, 302 );
%h = @{ $res->[1] };
ok(
$h{Location} eq 'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test1.example.com/AuthStrong', '' ),
'Redirection points to http://test1.example.com/AuthStrong'
)
or explain(
\%h,
'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test1.example.com/AuthStrong', '' )
);
count(3);
# Bad cookie
ok(
$res = $client->_get(
......@@ -70,9 +92,38 @@ ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 );
unlink(
't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock'
);
count(2);
# Required AuthnLevel = 1
ok(
$res = $client->_get(
'/AuthWeak', undef, 'test2.example.com', "lemonldap=$sessionId"
),
'Weak Authentified query'
);
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 );
count(2);
# Required AuthnLevel = 5
ok(
$res =
$client->_get( '/', undef, 'test2.example.com', "lemonldap=$sessionId" ),
'Default Authentified query'
);
ok( $res->[0] == 302, 'Code is 302' ) or explain( $res, 302 );
%h = @{ $res->[1] };
ok(
$h{Location} eq 'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test2.example.com/', '' ),
'Redirection points to http://test2.example.com/'
)
or explain(
\%h,
'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test2.example.com/', '' )
);
count(3);
done_testing( count() );
clean();
......
......@@ -9,6 +9,7 @@ init('Lemonldap::NG::Handler::Server');
my $res;
# Unauthentified query
# --------------------
ok( $res = $client->_get('/'), 'Unauthentified query' );
ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' );
ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 );
......@@ -23,17 +24,14 @@ ok(
'Location => http://auth.example.com/?url='
. encode_base64( 'http://test1.example.com/', '' )
);
count(4);
# Authentified queries
# --------------------
# Authorized query
ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ),
'Authentified query' );
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 );
count(2);
# Check headers
......@@ -46,9 +44,33 @@ count(1);
ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ),
'Denied query' );
ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 );
count(2);
# Required AuthnLevel = 1
ok( $res = $client->_get( '/AuthWeak', undef, undef, "lemonldap=$sessionId" ),
'Weak Authentified query' );
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 );
count(2);
# Required AuthnLevel = 5
ok(
$res = $client->_get( '/AuthStrong', undef, undef, "lemonldap=$sessionId" ),
'Strong Authentified query'
);
ok( $res->[0] == 302, 'Code is 302' ) or explain( $res, 302 );
%h = @{ $res->[1] };
ok(
$h{Location} eq 'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test1.example.com/AuthStrong', '' ),
'Redirection points to http://test1.example.com/AuthStrong'
)
or explain(
\%h,
'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test1.example.com/AuthStrong', '' )
);
count(3);
# Bad cookie
ok(
$res = $client->_get(
......@@ -63,9 +85,38 @@ ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 );
unlink(
't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock'
);
count(2);
# Required AuthnLevel = 1
ok(
$res = $client->_get(
'/AuthWeak', undef, 'test2.example.com', "lemonldap=$sessionId"
),
'Weak Authentified query'
);
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 );
count(2);
# Required AuthnLevel = 5
ok(
$res =
$client->_get( '/', undef, 'test2.example.com', "lemonldap=$sessionId" ),
'Default Authentified query'
);
ok( $res->[0] == 302, 'Code is 302' ) or explain( $res, 302 );
%h = @{ $res->[1] };
ok(
$h{Location} eq 'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test2.example.com/', '' ),
'Redirection points to http://test2.example.com/'
)
or explain(
\%h,
'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test2.example.com/', '' )
);
count(3);
done_testing( count() );
clean();
......@@ -34,7 +34,6 @@ count(4);
ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ),
'Authentified query' );
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 );
count(2);
# Check headers
......@@ -49,9 +48,33 @@ count(2);
ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ),
'Denied query' );
ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 );
count(2);
# Required AuthnLevel = 1
ok( $res = $client->_get( '/AuthWeak', undef, undef, "lemonldap=$sessionId" ),
'Weak Authentified query' );
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 );
count(2);
# Required AuthnLevel = 5
ok(
$res = $client->_get( '/AuthStrong', undef, undef, "lemonldap=$sessionId" ),
'Strong Authentified query'
);
ok( $res->[0] == 401, 'Code is 401' ) or explain( $res, 401 );
%h = @{ $res->[1] };
ok(
$h{Location} eq 'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test1.example.com/AuthStrong', '' ),
'Redirection points to http://test1.example.com/AuthStrong'
)
or explain(
\%h,
'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test1.example.com/AuthStrong', '' )
);
count(3);
# Bad cookie
ok(
$res = $client->_get(
......@@ -66,9 +89,38 @@ ok( $res->[0] == 401, 'Code is 401' ) or explain( $res->[0], 401 );
unlink(
't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock'
);
count(2);
# Required AuthnLevel = 1
ok(
$res = $client->_get(
'/AuthWeak', undef, 'test2.example.com', "lemonldap=$sessionId"
),
'Weak Authentified query'
);
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 );
count(2);
# Required AuthnLevel = 5
ok(
$res =
$client->_get( '/', undef, 'test2.example.com', "lemonldap=$sessionId" ),
'Default Authentified query'
);
ok( $res->[0] == 401, 'Code is 401' ) or explain( $res, 401 );
%h = @{ $res->[1] };
ok(
$h{Location} eq 'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test2.example.com/', '' ),
'Redirection points to http://test2.example.com/'
)
or explain(
\%h,
'http://auth.example.com//upgradesession?url='
. encode_base64( 'http://test2.example.com/', '' )
);
count(3);
done_testing( count() );
clean();
......@@ -41,12 +41,15 @@
"default": "$uid eq \"dwho\""
},
"test1.example.com": {
"^/AuthStrong(?#AuthnLevel=5)": "accept",
"^/AuthWeak(?#AuthnLevel=1)": "accept",
"^/logout": "logout_sso",
"^/deny": "deny",
"^/user_(\\w+)/": "$uid eq $_rulematch[1]",
"default": "accept"
},
"test2.example.com": {
"^/AuthWeak(?#AuthnLevel=1)": "accept",
"^/logout": "logout_sso",
"default": "accept"
},
......@@ -61,5 +64,10 @@
"portal": "http://auth.example.com/",
"reloadUrls": {},
"userDB": "Demo",
"vhostOptions": {
"test2.example.com": {
"vhostAuthnLevel": 5
}
},
"whatToTrace": "_whatToTrace"
}
......@@ -1254,6 +1254,9 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
'default' => 3,
'type' => 'int'
},
'forceGlobalStorageIssuerOTT' => {
'type' => 'bool'
},
'forceGlobalStorageUpgradeOTT' => {
'type' => 'bool'
},
......@@ -3032,10 +3035,6 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
},
'type' => 'file'
},
'samlIdPResolveCookie' => {
'default' => 'lemonldapidp',
'type' => 'text'
},
'samlIDPSSODescriptorArtifactResolutionServiceArtifact' => {
'default' =>
'1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/artifact',
......
......@@ -553,6 +553,11 @@ sub attributes {
documentation =>
'Avoid asking confirmation when an Issuer asks to renew auth',
},
forceGlobalStorageIssuerOTT => {
type => 'bool',
documentation =>
'Force Issuer tokens be stored into Global Storage',
},
handlerInternalCache => {
type => 'int',
default => 15,
......@@ -1532,7 +1537,7 @@ sub attributes {
forceGlobalStorageUpgradeOTT => {
type => 'bool',
documentation =>
'Force upgrade tokens be stored into Global Storage',
'Force Upgrade tokens be stored into Global Storage',
},
# 2F
......@@ -2319,11 +2324,6 @@ sub attributes {
documentation =>
'Use certificate instead of public key in SAML responses',
},
samlIdPResolveCookie => {
type => 'text',
default => 'lemonldapidp',
documentation => 'SAML IDP resolution cookie',
},
samlMetadataForceUTF8 => {
default => 1,
type => 'bool',
......
......@@ -1138,7 +1138,6 @@ sub tree {
title => 'samlAdvanced',
help => 'samlservice.html#advanced',
nodes => [
'samlIdPResolveCookie',
'samlMetadataForceUTF8',
'samlStorage',
'samlStorageOptions',
......
......@@ -266,6 +266,7 @@ sub _scanNodes {
$leaf->{comment}
? "(?#$leaf->{comment})$leaf->{re}"
: $leaf->{re};
$k .= "(?#AuthnLevel=$leaf->{level})" if $leaf->{level};
$self->set( $target, $key, $k, $leaf->{data} );
}
else {
......
......@@ -592,6 +592,10 @@ llapp.controller 'TreeCtrl', [
if a.template
a._nodes = templates a.template, a.title
node.nodes.push a
if a.type.match /^rule$/
console.log "Parse rule AuthnLevel as integer"
if a.level and typeof a.level == 'string'
a.level = parseInt(a.level, 10)
d.resolve 'OK'
$scope.waiting = false
, (response) ->
......
......@@ -17,6 +17,10 @@
<th><span trspan="rule"></span></th>
<td><textarea rows="3" id="hashvalueinput" class="form-control" ng-model="currentNode.data"/></td>
</tr>
<tr ng-if="currentNode.re!='default'">
<th><span trspan="ruleAuthnLevel"></span></th>
<td><input id="ruleAuthnLevel" type="number" class="form-control" ng-model="currentNode.level"/></td>
</tr>
</table>
</div>
<script type="text/menu">
......
......@@ -7,7 +7,8 @@
<tr>
<th width="20%" trspan="comments"></th>
<th width="30%" trspan="regexps"></th>
<th width="50%" trspan="rules"></th>
<th width="40%" trspan="rules"></th>
<th width="7%" trspan="rulesAuthnLevel"></th>
<th />
</tr>
</thead>
......@@ -28,6 +29,12 @@
<td>
<input class="form-control" ng-model="s.data"/>
</td>
<td ng-if="s.re!='default'">
<input type="number" class="form-control" ng-model="s.level"/>
</td>
<td ng-if="s.re=='default'">
<input class="form-control" placeholder="defaultLevel" readonly/>
</td>
<td>
<span ng-if="s.re!='default'" class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newRule'})"/>
......
(function(){var F;F={authParams:function(C,b,y){var n,o,x,e,s;for(s=[],n=0,o=(e=y.nodes).length;n<o;n++)x=e[n],s.push(C.ge