Merge branch 'extra2f' into 'v2.0'
Allow multi instanciation of 2F modules (#1860) See merge request lemonldap-ng/lemonldap-ng!87
This commit is contained in:
commit
349a40717b
|
@ -23,7 +23,7 @@ use constant HANDLERSECTION => "handler";
|
|||
use constant MANAGERSECTION => "manager";
|
||||
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
|
||||
use constant APPLYSECTION => "apply";
|
||||
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node)|S(?:erviceMetaDataAuthnContext|torageOptions))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars)|c(?:as(?:S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions)|A(?:ppMetaData(?:(?:ExportedVar|Option)s|Node)|ttributes))|(?:ustomAddParam|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
|
||||
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node)|S(?:erviceMetaDataAuthnContext|torageOptions))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions)|A(?:ppMetaData(?:(?:ExportedVar|Option)s|Node)|ttributes))|(?:ustomAddParam|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
|
||||
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|ingle(?:Session(?:UserByIP)?|(?:UserBy)?IP)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|howLanguages|slByAjax)|o(?:idc(?:ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|RPMetaDataOptions(?:LogoutSessionRequired|BypassConsent|RequirePKCE|Public)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:PersistentInfo|EmptyValues))?|State|XSS)|o(?:ntextSwitchingStopWithLogout|rsEnabled)|da)|p(?:ortal(?:ErrorOn(?:ExpiredSession|MailNotFound)|DisplayRe(?:setPassword|gister)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl)|oginHistoryEnabled)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|no(?:tif(?:ication(?:Server)?|y(?:Deleted|Other))|AjaxHook)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|rest(?:(?:Session|Config)Server|ExportSecretKeys)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs)|bruteForceProtection)$/;
|
||||
|
||||
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );
|
||||
|
|
|
@ -698,6 +698,25 @@ sub combModules {
|
|||
return $self->sendJSONresponse( $req, $res );
|
||||
}
|
||||
|
||||
sub sfExtra {
|
||||
my ( $self, $req, $key ) = @_;
|
||||
return $self->sendError( $req, 'Subkeys forbidden for sfExtra', 400 )
|
||||
if ($key);
|
||||
my $val = $self->getConfKey( $req, 'sfExtra' ) // {};
|
||||
my $res = [];
|
||||
foreach my $mod ( keys %$val ) {
|
||||
my $tmp;
|
||||
$tmp->{title} = $mod;
|
||||
$tmp->{id} = "sfExtra/$mod";
|
||||
$tmp->{type} = 'sfExtra';
|
||||
$tmp->{data}->{$_} = $val->{$mod}->{$_} foreach (qw(type rule));
|
||||
my $over = $val->{$mod}->{over} // {};
|
||||
$tmp->{data}->{over} = [ map { [ $_, $over->{$_} ] } keys %$over ];
|
||||
push @$res, $tmp;
|
||||
}
|
||||
return $self->sendJSONresponse( $req, $res );
|
||||
}
|
||||
|
||||
# 33 - Root queries
|
||||
# -----------
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ our $specialNodeHash = {
|
|||
};
|
||||
|
||||
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|fExtra)|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|S(?:MTPTLSOpts|SLVarIf))';
|
||||
our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaDataNode|virtualHost)s';
|
||||
our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:UserAttribut|Servic|Rul)e|ExportedVars)';
|
||||
our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)';
|
||||
|
|
|
@ -3212,6 +3212,26 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
'default' => '::2F::Engines::Default',
|
||||
'type' => 'text'
|
||||
},
|
||||
'sfExtra' => {
|
||||
'keyTest' => qr/^\w+$/,
|
||||
'select' => [ {
|
||||
'k' => 'Mail2F',
|
||||
'v' => 'E-Mail'
|
||||
},
|
||||
{
|
||||
'k' => 'REST',
|
||||
'v' => 'REST'
|
||||
},
|
||||
{
|
||||
'k' => 'Ext2F',
|
||||
'v' => 'External'
|
||||
}
|
||||
],
|
||||
'test' => sub {
|
||||
1;
|
||||
},
|
||||
'type' => 'sfExtraContainer'
|
||||
},
|
||||
'sfRemovedMsgRule' => {
|
||||
'default' => 0,
|
||||
'type' => 'boolOrExpr'
|
||||
|
|
|
@ -3278,6 +3278,17 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
{ k => 'Custom', v => 'customModule' },
|
||||
],
|
||||
},
|
||||
sfExtra => {
|
||||
type => 'sfExtraContainer',
|
||||
keyTest => qr/^\w+$/,
|
||||
test => sub { 1 },
|
||||
documentation => 'Extra second factors',
|
||||
select => [
|
||||
{ k => 'Mail2F', v => 'E-Mail' },
|
||||
{ k => 'REST', v => 'REST' },
|
||||
{ k => 'Ext2F', v => 'External' },
|
||||
],
|
||||
},
|
||||
|
||||
# Custom auth modules
|
||||
customAuth => {
|
||||
|
|
|
@ -775,6 +775,7 @@ sub tree {
|
|||
],
|
||||
},
|
||||
'sfRequired',
|
||||
'sfExtra',
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -69,7 +69,7 @@ sub addRoutes {
|
|||
qw(virtualHosts samlIDPMetaDataNodes samlSPMetaDataNodes
|
||||
applicationList oidcOPMetaDataNodes oidcRPMetaDataNodes
|
||||
casSrvMetaDataNodes casAppMetaDataNodes
|
||||
authChoiceModules grantSessionRules combModules
|
||||
authChoiceModules grantSessionRules combModules sfExtra
|
||||
openIdIDPList)
|
||||
]
|
||||
},
|
||||
|
|
|
@ -793,6 +793,25 @@ sub _scanNodes {
|
|||
next;
|
||||
}
|
||||
|
||||
# sfExtra: just to replace "over" key
|
||||
if ( $name eq 'sfExtra' ) {
|
||||
hdebug(' sfExtra');
|
||||
$self->newConf->{$name} = {};
|
||||
foreach my $node ( @{ $leaf->{nodes} } ) {
|
||||
my $tmp;
|
||||
$tmp->{$_} = $node->{data}->{$_} foreach (qw(type rule));
|
||||
$tmp->{over} = {};
|
||||
foreach ( @{ $node->{data}->{over} } ) {
|
||||
$tmp->{over}->{ $_->[0] } = $_->[1];
|
||||
}
|
||||
$self->newConf->{$name}->{ $node->{title} } = $tmp;
|
||||
}
|
||||
|
||||
# TODO: check changes
|
||||
$self->confChanged(1);
|
||||
next;
|
||||
}
|
||||
|
||||
$subNodes //= [];
|
||||
my $count = 0;
|
||||
my @old = (
|
||||
|
|
|
@ -328,6 +328,23 @@ llapp.controller 'TreeCtrl', [
|
|||
over: []
|
||||
$scope.execFilters $scope._findScopeByKey 'authParams'
|
||||
|
||||
$scope.newSfExtra = ->
|
||||
node = $scope._findContainer()
|
||||
node.nodes.push
|
||||
id: "#{node.id}/n#{id++}"
|
||||
title: 'new'
|
||||
type: 'sfExtra'
|
||||
data:
|
||||
type: ''
|
||||
rule: ''
|
||||
over: []
|
||||
|
||||
|
||||
$scope.newSfOver = ->
|
||||
d = $scope.currentNode.data
|
||||
d.over = [] unless d.over
|
||||
d.over.push ["new#{id++}", '']
|
||||
|
||||
$scope.newCmbOver = ->
|
||||
d = $scope.currentNode.data
|
||||
d.over = [] unless d.over
|
||||
|
@ -624,16 +641,17 @@ llapp.controller 'TreeCtrl', [
|
|||
node = scope.$modelValue
|
||||
# regexp-assemble of:
|
||||
# authChoice
|
||||
# keyText
|
||||
# cmbModule
|
||||
# virtualHost
|
||||
# rule
|
||||
# menuCat
|
||||
# keyText
|
||||
# menuApp
|
||||
# menuCat
|
||||
# rule
|
||||
# samlAttribute
|
||||
# samlIDPMetaDataNode
|
||||
# samlSPMetaDataNode
|
||||
return if node.type and node.type.match /^(?:(?:saml(?:(?:ID|S)PMetaDataNod|Attribut)|(?:cmbMod|r)ul|authChoic)e|(?:virtualHos|keyTex)t|menu(?:App|Cat))$/ then true else false
|
||||
# sfExtra
|
||||
# virtualHost
|
||||
return if node.type and node.type.match /^(?:s(?:aml(?:(?:ID|S)PMetaDataNod|Attribut)e|fExtra)|(?:(?:cmbMod|r)ul|authChoic)e|(?:virtualHos|keyTex)t|menu(?:App|Cat))$/ then true else false
|
||||
|
||||
# RSA keys generation
|
||||
$scope.newRSAKey = ->
|
||||
|
|
54
lemonldap-ng-manager/site/htdocs/static/forms/sfExtra.html
Normal file
54
lemonldap-ng-manager/site/htdocs/static/forms/sfExtra.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{{currentNode.title}}</h3>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="25%" trspan="name"></th>
|
||||
<th width="25%" trspan="type"></th>
|
||||
<th width="50%" trspan="rule"></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input class="form-control" ng-model="currentNode.title" />
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-control" ng-model="currentNode.data.type">
|
||||
<option ng-repeat="item in _findContainer().select" ng-selected="item.k===currentNode.data.type" value="{{item.k}}">{{item.v}}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" ng-model="currentNode.data.rule" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4 trspan="overPrm"></h4>
|
||||
<table class="table">
|
||||
<tr ng-repeat="t in currentNode.data.over">
|
||||
<td>
|
||||
<input class="form-control" ng-model="t[0]" />
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" ng-model="t[1]" />
|
||||
</td>
|
||||
<td>
|
||||
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data.over,$index)"/>
|
||||
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newSfOver'})"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<script type="text/menu">
|
||||
[{
|
||||
"title": "newSfOver",
|
||||
"icon": "plus-sign"
|
||||
},{
|
||||
"title": "deleteEntry",
|
||||
"icon": "minus-sign"
|
||||
}]
|
||||
</script>
|
|
@ -0,0 +1,40 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{{translateTitle(currentNode)}}</h3>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="25%" trspan="name"></th>
|
||||
<th width="25%" trspan="type"></th>
|
||||
<th width="50%" trspan="rule"></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="s in currentNode.nodes">
|
||||
<td>
|
||||
<input class="form-control" ng-model="s.title" />
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-control" ng-model="s.data.type">
|
||||
<option ng-repeat="item in currentNode.select" ng-selected="item.k==s.data.type" value="{{item.k}}">{{item.v}}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" ng-model="s.data.rule" />
|
||||
</td>
|
||||
<td>
|
||||
<span 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:'newSfExtra'})"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script type="text/menu">
|
||||
[{
|
||||
"title": "newSfExtra",
|
||||
"icon": "plus-sign"
|
||||
}]
|
||||
</script>
|
|
@ -1,4 +1,4 @@
|
|||
// Generated by CoffeeScript 1.12.7
|
||||
// Generated by CoffeeScript 1.12.8
|
||||
|
||||
/*
|
||||
LemonLDAP::NG Manager client
|
||||
|
@ -382,6 +382,28 @@ This file contains:
|
|||
});
|
||||
return $scope.execFilters($scope._findScopeByKey('authParams'));
|
||||
};
|
||||
$scope.newSfExtra = function() {
|
||||
var node;
|
||||
node = $scope._findContainer();
|
||||
return node.nodes.push({
|
||||
id: node.id + "/n" + (id++),
|
||||
title: 'new',
|
||||
type: 'sfExtra',
|
||||
data: {
|
||||
type: '',
|
||||
rule: '',
|
||||
over: []
|
||||
}
|
||||
});
|
||||
};
|
||||
$scope.newSfOver = function() {
|
||||
var d;
|
||||
d = $scope.currentNode.data;
|
||||
if (!d.over) {
|
||||
d.over = [];
|
||||
}
|
||||
return d.over.push(["new" + (id++), '']);
|
||||
};
|
||||
$scope.newCmbOver = function() {
|
||||
var d;
|
||||
d = $scope.currentNode.data;
|
||||
|
@ -767,7 +789,7 @@ This file contains:
|
|||
$scope.keyWritable = function(scope) {
|
||||
var node;
|
||||
node = scope.$modelValue;
|
||||
if (node.type && node.type.match(/^(?:(?:saml(?:(?:ID|S)PMetaDataNod|Attribut)|(?:cmbMod|r)ul|authChoic)e|(?:virtualHos|keyTex)t|menu(?:App|Cat))$/)) {
|
||||
if (node.type && node.type.match(/^(?:s(?:aml(?:(?:ID|S)PMetaDataNod|Attribut)e|fExtra)|(?:(?:cmbMod|r)ul|authChoic)e|(?:virtualHos|keyTex)t|menu(?:App|Cat))$/)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -465,6 +465,8 @@
|
|||
"newPostVar":"متغير جديد",
|
||||
"newRSAKey":"مفاتيح جديدة",
|
||||
"newRule":"قاعدة جديدة",
|
||||
"newSfOver":"معايير جديدة",
|
||||
"newSfExtra":"New second factor",
|
||||
"newValue":"قيمة جديدة",
|
||||
"next":"التالى",
|
||||
"nginxCustomHandlers":"معالجات إنجن إكس المخصصة",
|
||||
|
@ -734,6 +736,7 @@
|
|||
"sessionStorage":"تخزين الجلسات",
|
||||
"sessionTitle":"محتوى الجلسة",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsgRule":"تفعيل",
|
||||
|
@ -1015,4 +1018,4 @@
|
|||
"samlRelayStateTimeout":"تناوب حالة مهلة الجلسة ",
|
||||
"samlUseQueryStringSpecific":"استخدام أسلوب query_string المعين",
|
||||
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -465,6 +465,8 @@
|
|||
"newPostVar":"New variable",
|
||||
"newRSAKey":"New keys",
|
||||
"newRule":"New rule",
|
||||
"newSfOver":"New parameter",
|
||||
"newSfExtra":"New second factor",
|
||||
"newValue":"New value",
|
||||
"next":"Next",
|
||||
"nginxCustomHandlers":"Custom Nginx handlers",
|
||||
|
@ -734,6 +736,7 @@
|
|||
"sessionStorage":"Sessions Storage",
|
||||
"sessionTitle":"Session content",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsgRule":"Activation",
|
||||
|
@ -1015,4 +1018,4 @@
|
|||
"samlRelayStateTimeout":"RelayState session timeout",
|
||||
"samlUseQueryStringSpecific":"Use specific query_string method",
|
||||
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -465,6 +465,8 @@
|
|||
"newPostVar":"New variable",
|
||||
"newRSAKey":"New keys",
|
||||
"newRule":"New rule",
|
||||
"newSfOver":"New parameter",
|
||||
"newSfExtra":"New second factor",
|
||||
"newValue":"New value",
|
||||
"next":"Next",
|
||||
"nginxCustomHandlers":"Custom Nginx handlers",
|
||||
|
@ -734,6 +736,7 @@
|
|||
"sessionStorage":"Sessions Storage",
|
||||
"sessionTitle":"Session content",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsgRule":"Activation",
|
||||
|
|
|
@ -465,6 +465,8 @@
|
|||
"newPostVar":"Nouvelle variable",
|
||||
"newRSAKey":"Nouvelles clefs",
|
||||
"newRule":"Nouvelle règle",
|
||||
"newSfOver":"Nouveau paramètre",
|
||||
"newSfExtra":"Nouveau second facteur",
|
||||
"newValue":"Nouvelle valeur",
|
||||
"next":"Suivante",
|
||||
"nginxCustomHandlers":"Handlers Nginx personnalisés",
|
||||
|
@ -734,6 +736,7 @@
|
|||
"sessionStorage":"Stockage des sessions",
|
||||
"sessionTitle":"Contenu de la session",
|
||||
"sfaTitle":"Seconds Facteurs d'Authentification",
|
||||
"sfExtra":"Seconds Facteurs additionnels",
|
||||
"sfRequired":"Exiger 2FA",
|
||||
"sfRemovedNotification":"Afficher un message si un SF expiré a été supprimé",
|
||||
"sfRemovedMsgRule":"Activation",
|
||||
|
|
|
@ -465,6 +465,8 @@
|
|||
"newPostVar":"Nuova variabile",
|
||||
"newRSAKey":"Nuove chiavi",
|
||||
"newRule":"Nuova regola",
|
||||
"newSfOver":"Nuovo parametro",
|
||||
"newSfExtra":"New second factor",
|
||||
"newValue":"Nuovo valore",
|
||||
"next":"Seguente",
|
||||
"nginxCustomHandlers":"Gestori Custom Nginx",
|
||||
|
@ -734,6 +736,7 @@
|
|||
"sessionStorage":"Conservazione di sessioni",
|
||||
"sessionTitle":"Contenuto della sessione",
|
||||
"sfaTitle":"Autenticazione a due fattori",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Richiedi 2FA",
|
||||
"sfRemovedNotification":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsgRule":"Attivazione",
|
||||
|
@ -1015,4 +1018,4 @@
|
|||
"samlRelayStateTimeout":"Timeout di sessione di RelayState",
|
||||
"samlUseQueryStringSpecific":"Utilizza il metodo specifico query_string",
|
||||
"samlOverrideIDPEntityID":"Sostituisci l'ID entità quando agisce come IDP"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -465,6 +465,8 @@
|
|||
"newPostVar":"Biến mới",
|
||||
"newRSAKey":"Khóa mới",
|
||||
"newRule":"Quy tắc mới",
|
||||
"newSfOver":"Tham số mới",
|
||||
"newSfExtra":"New second factor",
|
||||
"newValue":"Giá trị mới",
|
||||
"next":"Tiếp theo",
|
||||
"nginxCustomHandlers":"Tùy chỉnh trình xử lý Nginx ",
|
||||
|
@ -734,6 +736,7 @@
|
|||
"sessionStorage":"Sessions lưu trữ",
|
||||
"sessionTitle":"Nội dung phiên",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsgRule":"Kích hoạt",
|
||||
|
@ -1015,4 +1018,4 @@
|
|||
"samlRelayStateTimeout":"Thời gian hết hạn phiên RelayState ",
|
||||
"samlUseQueryStringSpecific":"Sử dụng phương pháp query_string cụ thể",
|
||||
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -465,6 +465,8 @@
|
|||
"newPostVar":"New variable",
|
||||
"newRSAKey":"New keys",
|
||||
"newRule":"New rule",
|
||||
"newSfOver":"New parameter",
|
||||
"newSfExtra":"New second factor",
|
||||
"newValue":"New value",
|
||||
"next":"Next",
|
||||
"nginxCustomHandlers":"Custom Nginx handlers",
|
||||
|
@ -734,6 +736,7 @@
|
|||
"sessionStorage":"Sessions Storage",
|
||||
"sessionTitle":"Session content",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsgRule":"激活",
|
||||
|
@ -1015,4 +1018,4 @@
|
|||
"samlRelayStateTimeout":"RelayState session timeout",
|
||||
"samlUseQueryStringSpecific":"Use specific query_string method",
|
||||
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
38
lemonldap-ng-manager/t/17-extra2f.t
Normal file
38
lemonldap-ng-manager/t/17-extra2f.t
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Verify that bas changes are detected
|
||||
|
||||
use Test::More;
|
||||
use strict;
|
||||
use JSON;
|
||||
require 't/test-lib.pm';
|
||||
|
||||
my $struct = 't/jsonfiles/17-extra2f.json';
|
||||
|
||||
sub body {
|
||||
return IO::File->new( $struct, 'r' );
|
||||
}
|
||||
|
||||
unlink 't/conf/lmConf-2.json';
|
||||
mkdir 't/sessions';
|
||||
|
||||
my ( $res, $resBody );
|
||||
ok( $res = &client->_post( '/confs/', 'cfgNum=1', &body, 'application/json' ),
|
||||
"Request succeed" );
|
||||
ok( $res->[0] == 200, "Result code is 200" );
|
||||
ok( $resBody = from_json( $res->[2]->[0] ), "Result body contains JSON text" );
|
||||
ok( $resBody->{result} == 1, "JSON response contains \"result:1\"" )
|
||||
or print STDERR Dumper($res);
|
||||
|
||||
ok( $res = &client->_get( '/confs/2/sfExtra', 'application/json' ),
|
||||
'Get combModules' );
|
||||
ok( $resBody = from_json( $res->[2]->[0] ), "Result body contains JSON text" );
|
||||
|
||||
use Data::Dumper;
|
||||
print Dumper($resBody);
|
||||
|
||||
count(6);
|
||||
|
||||
done_testing( count() );
|
||||
|
||||
unlink 't/conf/lmConf-2.json';
|
||||
|
||||
`rm -rf t/sessions`;
|
4197
lemonldap-ng-manager/t/jsonfiles/17-extra2f.json
Normal file
4197
lemonldap-ng-manager/t/jsonfiles/17-extra2f.json
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -24,6 +24,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
|
|||
our $VERSION = '2.0.5';
|
||||
|
||||
extends 'Lemonldap::NG::Portal::Main::Plugin';
|
||||
with 'Lemonldap::NG::Portal::Lib::OverConf';
|
||||
|
||||
# INITIALIZATION
|
||||
|
||||
|
@ -93,6 +94,37 @@ sub init {
|
|||
}
|
||||
}
|
||||
|
||||
# Extra 2F modules
|
||||
$self->logger->debug('Processing Extra 2F modules');
|
||||
foreach my $extraKey (sort keys %{ $self->conf->{sfExtra} } ) {
|
||||
|
||||
my $moduleType = $self->conf->{sfExtra}->{$extraKey}->{type};
|
||||
next unless ($moduleType);
|
||||
|
||||
my %over = %{ $self->conf->{sfExtra}->{$extraKey}->{over} or {} };
|
||||
$self->logger->debug(
|
||||
"Loading extra 2F module $extraKey of type $moduleType");
|
||||
my $m =
|
||||
$self->loadPlugin( "::2F::$moduleType",
|
||||
{ sfPrefix => $extraKey, %over } )
|
||||
or return 0;
|
||||
|
||||
# Rule and prefix may be modified by 2F module, reread them
|
||||
my $rule = $self->conf->{sfExtra}->{$extraKey}->{rule} || 1;
|
||||
my $prefix = $m->prefix;
|
||||
|
||||
# Compile rule
|
||||
$rule = $self->p->HANDLER->substitute($rule);
|
||||
unless ( $rule = $self->p->HANDLER->buildSub($rule) ) {
|
||||
$self->error( 'External 2F rule error: '
|
||||
. $self->p->HANDLER->tsv->{jail}->error );
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Store module
|
||||
push @{ $self->{'sfModules'} }, { p => $prefix, m => $m, r => $rule };
|
||||
}
|
||||
|
||||
unless (
|
||||
$self->sfReq(
|
||||
$self->p->HANDLER->buildSub(
|
||||
|
|
|
@ -16,7 +16,7 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor';
|
|||
|
||||
# INITIALIZATION
|
||||
|
||||
has prefix => ( is => 'ro', default => 'ext' );
|
||||
has prefix => ( is => 'rw', default => 'ext' );
|
||||
has random => ( is => 'rw' );
|
||||
|
||||
sub init {
|
||||
|
@ -28,6 +28,8 @@ sub init {
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
$self->prefix( $self->conf->{sfPrefix} )
|
||||
if ( $self->conf->{sfPrefix} );
|
||||
$self->logo( $self->conf->{ext2fLogo} )
|
||||
if ( $self->conf->{ext2fLogo} );
|
||||
return $self->SUPER::init();
|
||||
|
@ -38,6 +40,8 @@ sub init {
|
|||
return 0;
|
||||
}
|
||||
$self->random( Lemonldap::NG::Common::Crypto::srandom() );
|
||||
$self->prefix( $self->conf->{sfPrefix} )
|
||||
if ( $self->conf->{sfPrefix} );
|
||||
$self->logo( $self->conf->{ext2fLogo} )
|
||||
if ( $self->conf->{ext2fLogo} );
|
||||
return $self->SUPER::init();
|
||||
|
@ -82,6 +86,7 @@ sub run {
|
|||
MAIN_LOGO => $self->conf->{portalMainLogo},
|
||||
SKIN => $self->p->getSkin($req),
|
||||
TOKEN => $token,
|
||||
TARGET => '/' . $self->prefix . '2fcheck',
|
||||
CHECKLOGINS => $checkLogins
|
||||
}
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor',
|
|||
|
||||
# INITIALIZATION
|
||||
|
||||
has prefix => ( is => 'ro', default => 'mail' );
|
||||
has prefix => ( is => 'rw', default => 'mail' );
|
||||
has random => (
|
||||
is => 'rw',
|
||||
default => sub {
|
||||
|
@ -47,6 +47,8 @@ sub init {
|
|||
}
|
||||
$self->logo( $self->conf->{mail2fLogo} )
|
||||
if ( $self->conf->{mail2fLogo} );
|
||||
$self->prefix( $self->conf->{sfPrefix} )
|
||||
if ( $self->conf->{sfPrefix} );
|
||||
return $self->SUPER::init();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor',
|
|||
|
||||
# INITIALIZATION
|
||||
|
||||
has prefix => ( is => 'ro', default => 'rest' );
|
||||
has prefix => ( is => 'rw', default => 'rest' );
|
||||
|
||||
has initAttrs => ( is => 'rw', default => sub { {} } );
|
||||
|
||||
|
@ -29,6 +29,8 @@ sub init {
|
|||
$self->logger->error('Missing REST verification URL');
|
||||
return 0;
|
||||
}
|
||||
$self->prefix( $self->conf->{sfPrefix} )
|
||||
if ( $self->conf->{sfPrefix} );
|
||||
$self->logo( $self->conf->{rest2fLogo} ) if ( $self->conf->{rest2fLogo} );
|
||||
foreach my $k ( keys %{ $self->conf->{rest2fInitArgs} } ) {
|
||||
my $attr = $self->conf->{rest2fInitArgs}->{$k};
|
||||
|
@ -92,7 +94,7 @@ sub run {
|
|||
MAIN_LOGO => $self->conf->{portalMainLogo},
|
||||
SKIN => $self->p->getSkin($req),
|
||||
TOKEN => $token,
|
||||
TARGET => '/rest2fcheck',
|
||||
TARGET => '/' . $self->prefix . '2fcheck',
|
||||
CHECKLOGINS => $checkLogins
|
||||
}
|
||||
);
|
||||
|
|
162
lemonldap-ng-portal/t/77-2F-Extra.t
Normal file
162
lemonldap-ng-portal/t/77-2F-Extra.t
Normal file
|
@ -0,0 +1,162 @@
|
|||
use Test::More;
|
||||
use strict;
|
||||
use IO::String;
|
||||
use Data::Dumper;
|
||||
|
||||
require 't/test-lib.pm';
|
||||
require 't/smtp.pm';
|
||||
|
||||
use_ok('Lemonldap::NG::Common::FormEncode');
|
||||
count(1);
|
||||
|
||||
my $client = LLNG::Manager::Test->new( {
|
||||
ini => {
|
||||
logLevel => 'error',
|
||||
authentication => 'Demo',
|
||||
userDB => 'Same',
|
||||
'sfExtra' => {
|
||||
'home' => {
|
||||
'over' => {
|
||||
mail2fCodeRegex => '\w{4}',
|
||||
mail2fLogo => 'home.jpg',
|
||||
},
|
||||
'rule' => '$uid eq "dwho" or $uid eq "msmith"',
|
||||
'type' => 'Mail2F'
|
||||
},
|
||||
'work' => {
|
||||
'over' => {
|
||||
mail2fLogo => 'work.jpg',
|
||||
mail2fCodeRegex => '\d{8}',
|
||||
},
|
||||
'rule' => '$uid eq "dwho" or $uid eq "rtyler"',
|
||||
'type' => 'Mail2F'
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
# Login with rtyler
|
||||
# -----------------
|
||||
ok(
|
||||
my $res = $client->_post(
|
||||
'/',
|
||||
IO::String->new('user=rtyler&password=rtyler'),
|
||||
length => 27,
|
||||
accept => 'text/html',
|
||||
),
|
||||
'Auth query'
|
||||
);
|
||||
count(1);
|
||||
|
||||
# Only "work" option is available
|
||||
my ( $host, $url, $query ) =
|
||||
expectForm( $res, undef, '/work2fcheck', 'token', 'code' );
|
||||
|
||||
ok(
|
||||
$res->[2]->[0] =~
|
||||
qr%<input name="code" value="" class="form-control" id="extcode" trplaceholder="code" autocomplete="off" />%,
|
||||
'Found EXTCODE input'
|
||||
) or print STDERR Dumper( $res->[2]->[0] );
|
||||
count(1);
|
||||
|
||||
ok( mail() =~ m%<b>(\d{8})</b>%, 'Found 2F code in mail' )
|
||||
or print STDERR Dumper( mail() );
|
||||
|
||||
my $code = $1;
|
||||
count(1);
|
||||
|
||||
$query =~ s/code=/code=${code}/;
|
||||
ok(
|
||||
$res = $client->_post(
|
||||
'/work2fcheck',
|
||||
IO::String->new($query),
|
||||
length => length($query),
|
||||
accept => 'text/html',
|
||||
),
|
||||
'Post code'
|
||||
);
|
||||
count(1);
|
||||
my $id = expectCookie($res);
|
||||
$client->logout($id);
|
||||
|
||||
clean_sessions();
|
||||
|
||||
# Login with dwho
|
||||
# ---------------
|
||||
ok(
|
||||
my $res = $client->_post(
|
||||
'/',
|
||||
IO::String->new('user=dwho&password=dwho'),
|
||||
length => 23,
|
||||
accept => 'text/html',
|
||||
),
|
||||
'Auth query'
|
||||
);
|
||||
count(1);
|
||||
|
||||
|
||||
# Expect choice page
|
||||
my ( $host, $url, $query ) =
|
||||
expectForm( $res, undef, '/2fchoice', 'token', 'checkLogins' );
|
||||
|
||||
ok(
|
||||
$res->[2]->[0] =~
|
||||
qq%<img src="/static/bootstrap/work.jpg" alt="work2F" title="work2F" />%,
|
||||
'Found work.png'
|
||||
) or print STDERR Dumper( $res->[2]->[0] );
|
||||
count(1);
|
||||
|
||||
ok(
|
||||
$res->[2]->[0] =~
|
||||
qq%<img src="/static/bootstrap/home.jpg" alt="home2F" title="home2F" />%,
|
||||
'Found home.png'
|
||||
) or print STDERR Dumper( $res->[2]->[0] );
|
||||
count(1);
|
||||
|
||||
$query .= '&sf=home';
|
||||
ok(
|
||||
$res = $client->_post(
|
||||
'/2fchoice',
|
||||
IO::String->new($query),
|
||||
length => length($query),
|
||||
accept => 'text/html',
|
||||
),
|
||||
'Post ext2f choice'
|
||||
);
|
||||
count(1);
|
||||
|
||||
my ( $host, $url, $query ) =
|
||||
expectForm( $res, undef, '/home2fcheck', 'token', 'code' );
|
||||
|
||||
ok(
|
||||
$res->[2]->[0] =~
|
||||
qr%<input name="code" value="" class="form-control" id="extcode" trplaceholder="code" autocomplete="off" />%,
|
||||
'Found EXTCODE input'
|
||||
) or print STDERR Dumper( $res->[2]->[0] );
|
||||
count(1);
|
||||
|
||||
ok( mail() =~ m%<b>(\w{4})</b>%, 'Found 2F code in mail' )
|
||||
or print STDERR Dumper( mail() );
|
||||
|
||||
my $code = $1;
|
||||
count(1);
|
||||
|
||||
$query =~ s/code=/code=${code}/;
|
||||
ok(
|
||||
$res = $client->_post(
|
||||
'/home2fcheck',
|
||||
IO::String->new($query),
|
||||
length => length($query),
|
||||
accept => 'text/html',
|
||||
),
|
||||
'Post code'
|
||||
);
|
||||
count(1);
|
||||
my $id = expectCookie($res);
|
||||
$client->logout($id);
|
||||
|
||||
clean_sessions();
|
||||
|
||||
done_testing( count() );
|
||||
|
Loading…
Reference in New Issue
Block a user