Merge branch 'v2.0'
This commit is contained in:
commit
4798683129
|
@ -40,8 +40,9 @@ useRedirectOnError = 0
|
||||||
enabledModules = conf, sessions, notifications, 2ndFA, viewer
|
enabledModules = conf, sessions, notifications, 2ndFA, viewer
|
||||||
protection = manager
|
protection = manager
|
||||||
|
|
||||||
viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout
|
viewerHiddenKeys = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout captcha_login_enabled
|
||||||
viewerAllowBrowser = 1
|
viewerAllowBrowser = 1
|
||||||
|
viewerAllowDiff = 1
|
||||||
|
|
||||||
staticPrefix = /static
|
staticPrefix = /static
|
||||||
languages = fr, en, vi, ar, de, it, zh
|
languages = fr, en, vi, ar, de, it, zh
|
||||||
|
|
|
@ -362,8 +362,9 @@ languages = fr, en, vi, ar
|
||||||
enabledModules = conf, sessions, notifications, 2ndFA, viewer
|
enabledModules = conf, sessions, notifications, 2ndFA, viewer
|
||||||
|
|
||||||
; Viewer options - Default values
|
; Viewer options - Default values
|
||||||
;viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes
|
;viewerHiddenKeys = samlIDPMetaDataNodes samlSPMetaDataNodes
|
||||||
;viewerAllowBrowser = 0
|
;viewerAllowBrowser = 0
|
||||||
|
;viewerAllowDiff = 0
|
||||||
|
|
||||||
;[node-handler]
|
;[node-handler]
|
||||||
;
|
;
|
||||||
|
|
|
@ -296,7 +296,7 @@ sub defaultValues {
|
||||||
'useRedirectOnError' => 1,
|
'useRedirectOnError' => 1,
|
||||||
'useSafeJail' => 1,
|
'useSafeJail' => 1,
|
||||||
'utotp2fActivation' => 0,
|
'utotp2fActivation' => 0,
|
||||||
'viewerHiddenPK' => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
|
'viewerHiddenKeys' => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
|
||||||
'webIDAuthnLevel' => 1,
|
'webIDAuthnLevel' => 1,
|
||||||
'webIDExportedVars' => {},
|
'webIDExportedVars' => {},
|
||||||
'whatToTrace' => 'uid',
|
'whatToTrace' => 'uid',
|
||||||
|
|
|
@ -174,7 +174,7 @@ body{background:#000;color:#fff;padding:10px 50px;font-family:sans-serif;}a{text
|
||||||
<body>
|
<body>
|
||||||
<h1>$title</h1>
|
<h1>$title</h1>
|
||||||
<p>$err</p>
|
<p>$err</p>
|
||||||
<center><a href=\"http://lemonldap-ng.org\">LemonLDAP::NG</a></center>'
|
<center><a href=\"https://lemonldap-ng.org\">LemonLDAP::NG</a></center>
|
||||||
</body>
|
</body>
|
||||||
</html>";
|
</html>";
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -58,7 +58,7 @@ sub logLevelInit {
|
||||||
$logger = $class->localConfig->{userLogger} || $logger;
|
$logger = $class->localConfig->{userLogger} || $logger;
|
||||||
eval "require $logger";
|
eval "require $logger";
|
||||||
die $@ if ($@);
|
die $@ if ($@);
|
||||||
$class->userLogger( $logger->new( $class->localConfig ), user => 1 );
|
$class->userLogger( $logger->new( $class->localConfig, user => 1 ) );
|
||||||
$class->logger->debug("User logger $logger loaded");
|
$class->logger->debug("User logger $logger loaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ site/coffee/llApp.coffee
|
||||||
site/coffee/manager.coffee
|
site/coffee/manager.coffee
|
||||||
site/coffee/notifications.coffee
|
site/coffee/notifications.coffee
|
||||||
site/coffee/sessions.coffee
|
site/coffee/sessions.coffee
|
||||||
|
site/coffee/viewDiff.coffee
|
||||||
site/coffee/viewer.coffee
|
site/coffee/viewer.coffee
|
||||||
site/htdocs/manager.fcgi
|
site/htdocs/manager.fcgi
|
||||||
site/htdocs/manager.psgi
|
site/htdocs/manager.psgi
|
||||||
|
@ -105,6 +106,7 @@ site/htdocs/static/forms/file.html
|
||||||
site/htdocs/static/forms/grant.html
|
site/htdocs/static/forms/grant.html
|
||||||
site/htdocs/static/forms/grantContainer.html
|
site/htdocs/static/forms/grantContainer.html
|
||||||
site/htdocs/static/forms/home.html
|
site/htdocs/static/forms/home.html
|
||||||
|
site/htdocs/static/forms/homeViewer.html
|
||||||
site/htdocs/static/forms/int.html
|
site/htdocs/static/forms/int.html
|
||||||
site/htdocs/static/forms/keyText.html
|
site/htdocs/static/forms/keyText.html
|
||||||
site/htdocs/static/forms/keyTextContainer.html
|
site/htdocs/static/forms/keyTextContainer.html
|
||||||
|
@ -158,6 +160,8 @@ site/htdocs/static/js/notifications.js
|
||||||
site/htdocs/static/js/notifications.min.js
|
site/htdocs/static/js/notifications.min.js
|
||||||
site/htdocs/static/js/sessions.js
|
site/htdocs/static/js/sessions.js
|
||||||
site/htdocs/static/js/sessions.min.js
|
site/htdocs/static/js/sessions.min.js
|
||||||
|
site/htdocs/static/js/viewDiff.js
|
||||||
|
site/htdocs/static/js/viewDiff.min.js
|
||||||
site/htdocs/static/js/viewer.js
|
site/htdocs/static/js/viewer.js
|
||||||
site/htdocs/static/js/viewer.min.js
|
site/htdocs/static/js/viewer.min.js
|
||||||
site/htdocs/static/languages/ar.json
|
site/htdocs/static/languages/ar.json
|
||||||
|
@ -191,6 +195,7 @@ site/templates/notifications.tpl
|
||||||
site/templates/scripts.tpl
|
site/templates/scripts.tpl
|
||||||
site/templates/sessions.tpl
|
site/templates/sessions.tpl
|
||||||
site/templates/tree.tpl
|
site/templates/tree.tpl
|
||||||
|
site/templates/viewDiff.tpl
|
||||||
site/templates/viewer.tpl
|
site/templates/viewer.tpl
|
||||||
t/02-HTML-template.t
|
t/02-HTML-template.t
|
||||||
t/03-HTML-forms.t
|
t/03-HTML-forms.t
|
||||||
|
@ -208,6 +213,7 @@ t/50-notifications-DBI.t
|
||||||
t/50-notifications.t
|
t/50-notifications.t
|
||||||
t/60-2ndfa.t
|
t/60-2ndfa.t
|
||||||
t/70-viewer.t
|
t/70-viewer.t
|
||||||
|
t/71-viewer-with-no-diff.t
|
||||||
t/80-attributes.t
|
t/80-attributes.t
|
||||||
t/90-translations.t
|
t/90-translations.t
|
||||||
t/99-pod.t
|
t/99-pod.t
|
||||||
|
@ -218,7 +224,8 @@ t/jsonfiles/11-modified-with-confirmation.json
|
||||||
t/jsonfiles/12-modified.json
|
t/jsonfiles/12-modified.json
|
||||||
t/jsonfiles/14-bad.json
|
t/jsonfiles/14-bad.json
|
||||||
t/jsonfiles/15-combination.json
|
t/jsonfiles/15-combination.json
|
||||||
|
t/jsonfiles/70-diff.json
|
||||||
t/lemonldap-ng-dbi.ini
|
t/lemonldap-ng-dbi.ini
|
||||||
t/lemonldap-ng-noBrowser.ini
|
t/lemonldap-ng-noDiff.ini
|
||||||
t/lemonldap-ng.ini
|
t/lemonldap-ng.ini
|
||||||
t/test-lib.pm
|
t/test-lib.pm
|
||||||
|
|
|
@ -24,6 +24,7 @@ extends 'Lemonldap::NG::Common::Conf::AccessLib',
|
||||||
|
|
||||||
has csp => ( is => 'rw' );
|
has csp => ( is => 'rw' );
|
||||||
has brw => ( is => 'rw', default => 0 );
|
has brw => ( is => 'rw', default => 0 );
|
||||||
|
has dif => ( is => 'rw', default => 0 );
|
||||||
|
|
||||||
## @method boolean init($args)
|
## @method boolean init($args)
|
||||||
# Launch initialization method
|
# Launch initialization method
|
||||||
|
@ -88,6 +89,7 @@ sub init {
|
||||||
"default-src 'self' $portal;frame-ancestors 'none';form-action 'self';"
|
"default-src 'self' $portal;frame-ancestors 'none';form-action 'self';"
|
||||||
);
|
);
|
||||||
$self->brw( $self->{viewerAllowBrowser} || $conf->{viewerAllowBrowser} );
|
$self->brw( $self->{viewerAllowBrowser} || $conf->{viewerAllowBrowser} );
|
||||||
|
$self->dif( $self->{viewerAllowDiff} || $conf->{viewerAllowDiff} );
|
||||||
$self->defaultRoute( $working[0]->defaultRoute );
|
$self->defaultRoute( $working[0]->defaultRoute );
|
||||||
|
|
||||||
# Find out more glyphicones at https://www.w3schools.com/icons/bootstrap_icons_glyphicons.asp
|
# Find out more glyphicones at https://www.w3schools.com/icons/bootstrap_icons_glyphicons.asp
|
||||||
|
@ -142,6 +144,7 @@ sub javascript {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
return
|
return
|
||||||
'var formPrefix=staticPrefix+"forms/";var confPrefix=scriptname+"confs/";var viewPrefix=scriptname+"view/";'
|
'var formPrefix=staticPrefix+"forms/";var confPrefix=scriptname+"confs/";var viewPrefix=scriptname+"view/";'
|
||||||
|
. 'var allowDiff=' . $self->dif . ';'
|
||||||
. ( $self->links ? 'var links=' . to_json( $self->links ) . ';' : '' )
|
. ( $self->links ? 'var links=' . to_json( $self->links ) . ';' : '' )
|
||||||
. (
|
. (
|
||||||
$self->menuLinks
|
$self->menuLinks
|
||||||
|
|
|
@ -3566,7 +3566,11 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
'type' => 'bool'
|
'type' => 'bool'
|
||||||
},
|
},
|
||||||
'viewerHiddenPK' => {
|
'viewerAllowDiff' => {
|
||||||
|
'default' => 0,
|
||||||
|
'type' => 'bool'
|
||||||
|
},
|
||||||
|
'viewerHiddenKeys' => {
|
||||||
'default' => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
|
'default' => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
|
||||||
'type' => 'text'
|
'type' => 'text'
|
||||||
},
|
},
|
||||||
|
|
|
@ -916,10 +916,10 @@ sub attributes {
|
||||||
},
|
},
|
||||||
|
|
||||||
# Viewer
|
# Viewer
|
||||||
viewerHiddenPK => {
|
viewerHiddenKeys => {
|
||||||
type => 'text',
|
type => 'text',
|
||||||
default => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
|
default => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
|
||||||
documentation => 'ConfTree hidden primary keys',
|
documentation => 'Hidden Conf keys',
|
||||||
flags => 'm',
|
flags => 'm',
|
||||||
},
|
},
|
||||||
viewerAllowBrowser => {
|
viewerAllowBrowser => {
|
||||||
|
@ -927,6 +927,11 @@ sub attributes {
|
||||||
default => 0,
|
default => 0,
|
||||||
documentation => 'Allow configuration browser',
|
documentation => 'Allow configuration browser',
|
||||||
},
|
},
|
||||||
|
viewerAllowDiff => {
|
||||||
|
type => 'bool',
|
||||||
|
default => 0,
|
||||||
|
documentation => 'Allow configuration diff',
|
||||||
|
},
|
||||||
|
|
||||||
# Notification
|
# Notification
|
||||||
oldNotifFormat => {
|
oldNotifFormat => {
|
||||||
|
|
|
@ -26,7 +26,7 @@ sub addRoutes {
|
||||||
$self->ua( Lemonldap::NG::Common::UserAgent->new($conf) );
|
$self->ua( Lemonldap::NG::Common::UserAgent->new($conf) );
|
||||||
|
|
||||||
my $hiddenPK = '';
|
my $hiddenPK = '';
|
||||||
$hiddenPK = $self->{viewerHiddenPK} || $conf->{viewerHiddenPK};
|
$hiddenPK = $self->{viewerHiddenKeys} || $conf->{viewerHiddenKeys};
|
||||||
my @enabledPK = ();
|
my @enabledPK = ();
|
||||||
my @keys = qw(virtualHosts samlIDPMetaDataNodes samlSPMetaDataNodes
|
my @keys = qw(virtualHosts samlIDPMetaDataNodes samlSPMetaDataNodes
|
||||||
applicationList oidcOPMetaDataNodes oidcRPMetaDataNodes
|
applicationList oidcOPMetaDataNodes oidcRPMetaDataNodes
|
||||||
|
@ -66,12 +66,15 @@ sub addRoutes {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Other keys
|
|
||||||
$self->addRoute( view => { ':cfgNum' => { '*' => 'getKey' } }, ['GET'] )
|
|
||||||
|
|
||||||
# Difference between confs
|
# Difference between confs
|
||||||
->addRoute( diff => { ':conf1' => { ':conf2' => 'diff' } } )
|
if ( $self->{viewerAllowDiff} || $conf->{viewerAllowDiff} ) {
|
||||||
->addRoute( 'diff.html', undef, ['GET'] );
|
$self->addRoute(
|
||||||
|
view => { diff => { ':conf1' => { ':conf2' => 'viewDiff' } } } )
|
||||||
|
->addRoute( 'viewDiff.html', undef, ['GET'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Other keys
|
||||||
|
$self->addRoute( view => { ':cfgNum' => { '*' => 'getKey' } }, ['GET'] );
|
||||||
}
|
}
|
||||||
|
|
||||||
sub getConfByNum {
|
sub getConfByNum {
|
||||||
|
@ -79,9 +82,43 @@ sub getConfByNum {
|
||||||
$self->SUPER::getConfByNum( $cfgNum, @args );
|
$self->SUPER::getConfByNum( $cfgNum, @args );
|
||||||
}
|
}
|
||||||
|
|
||||||
sub diff {
|
sub viewDiff {
|
||||||
my ( $self, $req, @path ) = @_;
|
my ( $self, $req, @path ) = @_;
|
||||||
$self->SUPER::diff( $req, @path );
|
return $self->sendError( $req, 'to many arguments in path info', 400 )
|
||||||
|
if (@path);
|
||||||
|
my @cfgNum =
|
||||||
|
( scalar( $req->param('conf1') ), scalar( $req->param('conf2') ) );
|
||||||
|
my @conf;
|
||||||
|
$self->logger->debug(" Loading confs");
|
||||||
|
|
||||||
|
# Load the 2 configurations
|
||||||
|
for ( my $i = 0 ; $i < 2 ; $i++ ) {
|
||||||
|
if ( %{ $self->currentConf }
|
||||||
|
and $cfgNum[$i] == $self->currentConf->{cfgNum} )
|
||||||
|
{
|
||||||
|
$conf[$i] = $self->currentConf;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$conf[$i] = $self->confAcc->getConf(
|
||||||
|
{ cfgNum => $cfgNum[$i], raw => 1, noCache => 1 } );
|
||||||
|
return $self->sendError(
|
||||||
|
$req,
|
||||||
|
"Configuration $cfgNum[$i] not available $Lemonldap::NG::Common::Conf::msg",
|
||||||
|
400
|
||||||
|
) unless ( $conf[$i] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require Lemonldap::NG::Manager::Conf::Diff;
|
||||||
|
my @res =
|
||||||
|
$self->Lemonldap::NG::Manager::Conf::Diff::diff( $conf[0], $conf[1] );
|
||||||
|
my $hiddenKeys = $self->{viewerHiddenKeys} || '';
|
||||||
|
$self->logger->debug("Deleting hidden Conf keys...");
|
||||||
|
foreach ( split /\s+/, $hiddenKeys ) {
|
||||||
|
$self->logger->debug("-> Delete $_");
|
||||||
|
delete $res[0]->{$_};
|
||||||
|
delete $res[1]->{$_};
|
||||||
|
}
|
||||||
|
return $self->sendJSONresponse( $req, [@res] );
|
||||||
}
|
}
|
||||||
|
|
||||||
sub rejectKey {
|
sub rejectKey {
|
||||||
|
|
215
lemonldap-ng-manager/site/coffee/viewDiff.coffee
Normal file
215
lemonldap-ng-manager/site/coffee/viewDiff.coffee
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
###
|
||||||
|
diff.html script
|
||||||
|
###
|
||||||
|
|
||||||
|
llapp = angular.module 'llngConfDiff', ['ui.tree', 'ui.bootstrap', 'llApp', 'ngCookies'] , ($rootScopeProvider) -> $rootScopeProvider.digestTtl(15)
|
||||||
|
llapp.controller 'DiffCtrl', [ '$scope', '$http', '$q', '$translator', '$location', ($scope, $http, $q, $translator, $location) ->
|
||||||
|
$scope.links = links
|
||||||
|
$scope.menulinks = menulinks
|
||||||
|
$scope.staticPrefix = staticPrefix
|
||||||
|
$scope.scriptname = scriptname
|
||||||
|
#$scope.formPrefix = formPrefix
|
||||||
|
$scope.availableLanguages = availableLanguages
|
||||||
|
$scope.waiting = true
|
||||||
|
$scope.showM = false
|
||||||
|
$scope.cfg = []
|
||||||
|
$scope.data = {}
|
||||||
|
$scope.currentNode = null
|
||||||
|
|
||||||
|
# Import translations functions
|
||||||
|
$scope.translateTitle = (node) ->
|
||||||
|
return $translator.translateField node, 'title'
|
||||||
|
$scope.translateP = $translator.translateP
|
||||||
|
$scope.translate = $translator.translate
|
||||||
|
|
||||||
|
$scope.toggle = (scope) ->
|
||||||
|
scope.toggle()
|
||||||
|
|
||||||
|
$scope.stoggle = (scope,node) ->
|
||||||
|
$scope.currentNode = node
|
||||||
|
scope.toggle()
|
||||||
|
|
||||||
|
# Handle menu items
|
||||||
|
$scope.menuClick = (button) ->
|
||||||
|
if button.popup
|
||||||
|
window.open button.popup
|
||||||
|
else
|
||||||
|
button.action = button.title unless button.action
|
||||||
|
switch typeof button.action
|
||||||
|
when 'function'
|
||||||
|
button.action $scope.currentNode, $scope
|
||||||
|
when 'string'
|
||||||
|
$scope[button.action]()
|
||||||
|
else
|
||||||
|
console.log typeof button.action
|
||||||
|
$scope.showM = false
|
||||||
|
|
||||||
|
# Function to change interface language
|
||||||
|
$scope.getLanguage = (lang) ->
|
||||||
|
$scope.lang = lang
|
||||||
|
$scope.init()
|
||||||
|
$scope.showM = false
|
||||||
|
|
||||||
|
# function `getCfg(b,n)`:
|
||||||
|
# Download configuration metadatas
|
||||||
|
#
|
||||||
|
#@param b local conf (0 or 1)
|
||||||
|
#@param n cfgNumber
|
||||||
|
getCfg = (b,n) ->
|
||||||
|
d = $q.defer()
|
||||||
|
if not $scope.cfg[b]? or $scope.cfg[b] != n
|
||||||
|
$http.get("#{confPrefix}#{n}").then (response) ->
|
||||||
|
if response and response.data
|
||||||
|
$scope.cfg[b] = response.data
|
||||||
|
date = new Date response.data.cfgDate * 1000
|
||||||
|
$scope.cfg[b].date = date.toLocaleString()
|
||||||
|
console.log "Metadatas of cfg #{n} loaded"
|
||||||
|
d.resolve 'OK'
|
||||||
|
else
|
||||||
|
d.reject response
|
||||||
|
, (response) ->
|
||||||
|
console.log response
|
||||||
|
d.reject 'NOK'
|
||||||
|
else
|
||||||
|
d.resolve()
|
||||||
|
return d.promise
|
||||||
|
|
||||||
|
# Intialization function
|
||||||
|
# Simply set $scope.waiting to false during $translator and tree root
|
||||||
|
# initialization
|
||||||
|
init = ->
|
||||||
|
$scope.message = null
|
||||||
|
$scope.currentNode = null
|
||||||
|
d = $q.defer()
|
||||||
|
$http.get("#{scriptname}view/diff/#{$scope.cfg[0].cfgNum}/#{$scope.cfg[1].cfgNum}").then (response) ->
|
||||||
|
data = []
|
||||||
|
data = readDiff(response.data[0],response.data[1])
|
||||||
|
$scope.data = buildTree(data)
|
||||||
|
$scope.message = ''
|
||||||
|
$scope.waiting = false
|
||||||
|
, (response) ->
|
||||||
|
$scope.message = "#{$scope.translate('error')} : #{response.statusLine}"
|
||||||
|
|
||||||
|
readDiff = (c1,c2,tr=true) ->
|
||||||
|
res = []
|
||||||
|
for k,v of c1
|
||||||
|
if tr
|
||||||
|
tmp =
|
||||||
|
title: $scope.translate(k)
|
||||||
|
id: k
|
||||||
|
else
|
||||||
|
tmp = title: k
|
||||||
|
unless k.match /^cfg(?:Num|Log|Author(?:IP)?|Date)$/
|
||||||
|
if v? and typeof v == 'object'
|
||||||
|
if v.constructor == 'array'
|
||||||
|
tmp.oldvalue = v
|
||||||
|
tmp.newvalue = c2[k]
|
||||||
|
else if typeof c2[k] == 'object'
|
||||||
|
tmp.nodes = readDiff c1[k],c2[k], false
|
||||||
|
else
|
||||||
|
tmp.oldnodes = toNodes v, 'old'
|
||||||
|
else
|
||||||
|
tmp.oldvalue = v
|
||||||
|
tmp.newvalue = c2[k]
|
||||||
|
res.push tmp
|
||||||
|
for k,v of c2
|
||||||
|
unless (k.match /^cfg(?:Num|Log|Author(?:IP)?|Date)$/) or c1[k]?
|
||||||
|
if tr
|
||||||
|
tmp =
|
||||||
|
title: $scope.translate(k)
|
||||||
|
id: k
|
||||||
|
else
|
||||||
|
tmp = title: k
|
||||||
|
if v? and typeof v == 'object'
|
||||||
|
if v.constructor == 'array'
|
||||||
|
tmp.newvalue = v
|
||||||
|
else
|
||||||
|
console.log "Iteration"
|
||||||
|
tmp.newnodes = toNodes v, 'new'
|
||||||
|
else
|
||||||
|
tmp.newvalue = v
|
||||||
|
res.push tmp
|
||||||
|
return res
|
||||||
|
|
||||||
|
toNodes = (c,s) ->
|
||||||
|
res = []
|
||||||
|
for k,v of c
|
||||||
|
tmp = title:k
|
||||||
|
if typeof v == 'object'
|
||||||
|
if v.constructor == 'array'
|
||||||
|
tmp["#{s}value"] = v
|
||||||
|
else
|
||||||
|
tmp["#{s}nodes"] = toNodes c[k], s
|
||||||
|
else
|
||||||
|
tmp["#{s}value"] = v
|
||||||
|
res.push tmp
|
||||||
|
return res
|
||||||
|
|
||||||
|
reverseTree = []
|
||||||
|
buildTree = (data) ->
|
||||||
|
return data unless reverseTree?
|
||||||
|
res = []
|
||||||
|
for elem in data
|
||||||
|
offset = res
|
||||||
|
path = if reverseTree[elem.id]? then reverseTree[elem.id].split '/' else ''
|
||||||
|
for node in path
|
||||||
|
if node.length > 0
|
||||||
|
if offset.length
|
||||||
|
found = -1
|
||||||
|
for n,i in offset
|
||||||
|
if n.id == node
|
||||||
|
#offset = n.nodes
|
||||||
|
found = i
|
||||||
|
if found != -1
|
||||||
|
offset = offset[found].nodes
|
||||||
|
else
|
||||||
|
offset.push
|
||||||
|
id: node
|
||||||
|
title: $scope.translate node
|
||||||
|
nodes: []
|
||||||
|
offset = offset[offset.length-1].nodes
|
||||||
|
else
|
||||||
|
offset.push
|
||||||
|
id: node
|
||||||
|
title: $scope.translate node
|
||||||
|
nodes: []
|
||||||
|
offset = offset[0].nodes
|
||||||
|
offset.push elem
|
||||||
|
return res
|
||||||
|
|
||||||
|
$scope.newDiff = ->
|
||||||
|
$location.path("/#{$scope.cfg[0].cfgNum}/#{$scope.cfg[1].cfgNum}")
|
||||||
|
|
||||||
|
pathEvent = (event, next, current) ->
|
||||||
|
n = next.match(new RegExp('#!?/(latest|[0-9]+)(?:/(latest|[0-9]+))?$'))
|
||||||
|
if n == null
|
||||||
|
$location.path '/latest'
|
||||||
|
else
|
||||||
|
$scope.waiting = true
|
||||||
|
$q.all [
|
||||||
|
$translator.init $scope.lang
|
||||||
|
$http.get("#{staticPrefix}reverseTree.json").then (response) ->
|
||||||
|
reverseTree = response.data
|
||||||
|
console.log "Structure loaded"
|
||||||
|
getCfg 0, n[1]
|
||||||
|
getCfg 1, n[2] if n[2]?
|
||||||
|
]
|
||||||
|
.then ->
|
||||||
|
if n[2]?
|
||||||
|
init()
|
||||||
|
else
|
||||||
|
if $scope.cfg[0].prev
|
||||||
|
$scope.cfg[1] = $scope.cfg[0]
|
||||||
|
getCfg 0, $scope.cfg[1].prev
|
||||||
|
.then ->
|
||||||
|
init()
|
||||||
|
else
|
||||||
|
$scope.data = []
|
||||||
|
$scope.waiting = false
|
||||||
|
, ->
|
||||||
|
$scope.message = $scope.translate('error')
|
||||||
|
$scope.waiting = false
|
||||||
|
true
|
||||||
|
|
||||||
|
$scope.$on '$locationChangeSuccess', pathEvent
|
||||||
|
]
|
|
@ -27,9 +27,10 @@ llapp.controller 'TreeCtrl', [
|
||||||
$scope.waiting = true
|
$scope.waiting = true
|
||||||
$scope.showM = false
|
$scope.showM = false
|
||||||
$scope.showT = false
|
$scope.showT = false
|
||||||
$scope.form = 'home'
|
$scope.form = 'homeViewer'
|
||||||
$scope.currentCfg = {}
|
$scope.currentCfg = {}
|
||||||
$scope.viewPrefix = window.viewPrefix
|
$scope.viewPrefix = window.viewPrefix
|
||||||
|
$scope.allowDiff = window.allowDiff
|
||||||
$scope.message = {}
|
$scope.message = {}
|
||||||
$scope.result = ''
|
$scope.result = ''
|
||||||
|
|
||||||
|
@ -133,7 +134,7 @@ llapp.controller 'TreeCtrl', [
|
||||||
|
|
||||||
# Display main form
|
# Display main form
|
||||||
$scope.home = ->
|
$scope.home = ->
|
||||||
$scope.form = 'home'
|
$scope.form = 'homeViewer'
|
||||||
$scope.showM = false
|
$scope.showM = false
|
||||||
|
|
||||||
# Download raw conf
|
# Download raw conf
|
||||||
|
@ -438,7 +439,7 @@ llapp.controller 'TreeCtrl', [
|
||||||
title: 'emptyConf'
|
title: 'emptyConf'
|
||||||
message: '__zeroConfExplanations__'
|
message: '__zeroConfExplanations__'
|
||||||
$scope.showModal 'message.html'
|
$scope.showModal 'message.html'
|
||||||
$scope.form = 'home'
|
$scope.form = 'homeViewer'
|
||||||
$scope.waiting = false
|
$scope.waiting = false
|
||||||
, readError
|
, readError
|
||||||
# Colorized link
|
# Colorized link
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
|
||||||
|
<span ng-if="!currentCfg.next" trspan="currentConfiguration"></span>
|
||||||
|
<span ng-if="currentCfg.next" trspan="loadedConfiguration"></span>
|
||||||
|
<i ng-if="currentCfg.prev && allowDiff">(<a trspan="diffWithPrevious" target="_blank" href="{{scriptname}}/viewDiff.html#!/{{currentCfg.cfgNum}}" role="link"></a>)</i>
|
||||||
|
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr>
|
||||||
|
<th><span trspan="number"></span></th>
|
||||||
|
<td>
|
||||||
|
<span id="cfgnum" class="label label-success" comment="{{translateP('__newCfgAvailable__')}}" ng-class="{'label-warning':currentCfg.next}">{{currentCfg.cfgNum}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-if="currentCfg.cfgAuthor">
|
||||||
|
<th><span trspan="author"></span></th>
|
||||||
|
<td>{{currentCfg.cfgAuthor}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-if="currentCfg.cfgAuthorIP">
|
||||||
|
<th><span trspan="authorIPAddress"></span></th>
|
||||||
|
<td>{{currentCfg.cfgAuthorIP}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-if="currentCfg.cfgDate">
|
||||||
|
<th><span trspan="date"></span></th>
|
||||||
|
<td>{{currentCfg.date}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-if="currentCfg.cfgVersion">
|
||||||
|
<th><span trspan="cfgVersion"></span></th>
|
||||||
|
<td>{{currentCfg.cfgVersion}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-if="currentCfg.cfgLog">
|
||||||
|
<th><span trspan="cfgLog"></span></th>
|
||||||
|
<td id="cfglog">{{currentCfg.cfgLog}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script type="text/menu">
|
||||||
|
[{
|
||||||
|
"title": "downloadIt",
|
||||||
|
"action": "downloadConf",
|
||||||
|
"icon": "export"
|
||||||
|
},{
|
||||||
|
"title": "restore",
|
||||||
|
"icon": "import"
|
||||||
|
}]
|
||||||
|
</script>
|
274
lemonldap-ng-manager/site/htdocs/static/js/viewDiff.js
Normal file
274
lemonldap-ng-manager/site/htdocs/static/js/viewDiff.js
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
// Generated by CoffeeScript 1.12.7
|
||||||
|
|
||||||
|
/*
|
||||||
|
diff.html script
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var llapp;
|
||||||
|
|
||||||
|
llapp = angular.module('llngConfDiff', ['ui.tree', 'ui.bootstrap', 'llApp', 'ngCookies'], function($rootScopeProvider) {
|
||||||
|
return $rootScopeProvider.digestTtl(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
llapp.controller('DiffCtrl', [
|
||||||
|
'$scope', '$http', '$q', '$translator', '$location', function($scope, $http, $q, $translator, $location) {
|
||||||
|
var buildTree, getCfg, init, pathEvent, readDiff, reverseTree, toNodes;
|
||||||
|
$scope.links = links;
|
||||||
|
$scope.menulinks = menulinks;
|
||||||
|
$scope.staticPrefix = staticPrefix;
|
||||||
|
$scope.scriptname = scriptname;
|
||||||
|
$scope.availableLanguages = availableLanguages;
|
||||||
|
$scope.waiting = true;
|
||||||
|
$scope.showM = false;
|
||||||
|
$scope.cfg = [];
|
||||||
|
$scope.data = {};
|
||||||
|
$scope.currentNode = null;
|
||||||
|
$scope.translateTitle = function(node) {
|
||||||
|
return $translator.translateField(node, 'title');
|
||||||
|
};
|
||||||
|
$scope.translateP = $translator.translateP;
|
||||||
|
$scope.translate = $translator.translate;
|
||||||
|
$scope.toggle = function(scope) {
|
||||||
|
return scope.toggle();
|
||||||
|
};
|
||||||
|
$scope.stoggle = function(scope, node) {
|
||||||
|
$scope.currentNode = node;
|
||||||
|
return scope.toggle();
|
||||||
|
};
|
||||||
|
$scope.menuClick = function(button) {
|
||||||
|
if (button.popup) {
|
||||||
|
window.open(button.popup);
|
||||||
|
} else {
|
||||||
|
if (!button.action) {
|
||||||
|
button.action = button.title;
|
||||||
|
}
|
||||||
|
switch (typeof button.action) {
|
||||||
|
case 'function':
|
||||||
|
button.action($scope.currentNode, $scope);
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
$scope[button.action]();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(typeof button.action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $scope.showM = false;
|
||||||
|
};
|
||||||
|
$scope.getLanguage = function(lang) {
|
||||||
|
$scope.lang = lang;
|
||||||
|
$scope.init();
|
||||||
|
return $scope.showM = false;
|
||||||
|
};
|
||||||
|
getCfg = function(b, n) {
|
||||||
|
var d;
|
||||||
|
d = $q.defer();
|
||||||
|
if (($scope.cfg[b] == null) || $scope.cfg[b] !== n) {
|
||||||
|
$http.get("" + confPrefix + n).then(function(response) {
|
||||||
|
var date;
|
||||||
|
if (response && response.data) {
|
||||||
|
$scope.cfg[b] = response.data;
|
||||||
|
date = new Date(response.data.cfgDate * 1000);
|
||||||
|
$scope.cfg[b].date = date.toLocaleString();
|
||||||
|
console.log("Metadatas of cfg " + n + " loaded");
|
||||||
|
return d.resolve('OK');
|
||||||
|
} else {
|
||||||
|
return d.reject(response);
|
||||||
|
}
|
||||||
|
}, function(response) {
|
||||||
|
console.log(response);
|
||||||
|
return d.reject('NOK');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
d.resolve();
|
||||||
|
}
|
||||||
|
return d.promise;
|
||||||
|
};
|
||||||
|
init = function() {
|
||||||
|
var d;
|
||||||
|
$scope.message = null;
|
||||||
|
$scope.currentNode = null;
|
||||||
|
d = $q.defer();
|
||||||
|
return $http.get(scriptname + "view/diff/" + $scope.cfg[0].cfgNum + "/" + $scope.cfg[1].cfgNum).then(function(response) {
|
||||||
|
var data;
|
||||||
|
data = [];
|
||||||
|
data = readDiff(response.data[0], response.data[1]);
|
||||||
|
$scope.data = buildTree(data);
|
||||||
|
$scope.message = '';
|
||||||
|
return $scope.waiting = false;
|
||||||
|
}, function(response) {
|
||||||
|
return $scope.message = ($scope.translate('error')) + " : " + response.statusLine;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
readDiff = function(c1, c2, tr) {
|
||||||
|
var k, res, tmp, v;
|
||||||
|
if (tr == null) {
|
||||||
|
tr = true;
|
||||||
|
}
|
||||||
|
res = [];
|
||||||
|
for (k in c1) {
|
||||||
|
v = c1[k];
|
||||||
|
if (tr) {
|
||||||
|
tmp = {
|
||||||
|
title: $scope.translate(k),
|
||||||
|
id: k
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
tmp = {
|
||||||
|
title: k
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!k.match(/^cfg(?:Num|Log|Author(?:IP)?|Date)$/)) {
|
||||||
|
if ((v != null) && typeof v === 'object') {
|
||||||
|
if (v.constructor === 'array') {
|
||||||
|
tmp.oldvalue = v;
|
||||||
|
tmp.newvalue = c2[k];
|
||||||
|
} else if (typeof c2[k] === 'object') {
|
||||||
|
tmp.nodes = readDiff(c1[k], c2[k], false);
|
||||||
|
} else {
|
||||||
|
tmp.oldnodes = toNodes(v, 'old');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tmp.oldvalue = v;
|
||||||
|
tmp.newvalue = c2[k];
|
||||||
|
}
|
||||||
|
res.push(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (k in c2) {
|
||||||
|
v = c2[k];
|
||||||
|
if (!((k.match(/^cfg(?:Num|Log|Author(?:IP)?|Date)$/)) || (c1[k] != null))) {
|
||||||
|
if (tr) {
|
||||||
|
tmp = {
|
||||||
|
title: $scope.translate(k),
|
||||||
|
id: k
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
tmp = {
|
||||||
|
title: k
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ((v != null) && typeof v === 'object') {
|
||||||
|
if (v.constructor === 'array') {
|
||||||
|
tmp.newvalue = v;
|
||||||
|
} else {
|
||||||
|
console.log("Iteration");
|
||||||
|
tmp.newnodes = toNodes(v, 'new');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tmp.newvalue = v;
|
||||||
|
}
|
||||||
|
res.push(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
toNodes = function(c, s) {
|
||||||
|
var k, res, tmp, v;
|
||||||
|
res = [];
|
||||||
|
for (k in c) {
|
||||||
|
v = c[k];
|
||||||
|
tmp = {
|
||||||
|
title: k
|
||||||
|
};
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
if (v.constructor === 'array') {
|
||||||
|
tmp[s + "value"] = v;
|
||||||
|
} else {
|
||||||
|
tmp[s + "nodes"] = toNodes(c[k], s);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tmp[s + "value"] = v;
|
||||||
|
}
|
||||||
|
res.push(tmp);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
reverseTree = [];
|
||||||
|
buildTree = function(data) {
|
||||||
|
var elem, found, i, j, l, len, len1, len2, m, n, node, offset, path, res;
|
||||||
|
if (reverseTree == null) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
res = [];
|
||||||
|
for (j = 0, len = data.length; j < len; j++) {
|
||||||
|
elem = data[j];
|
||||||
|
offset = res;
|
||||||
|
path = reverseTree[elem.id] != null ? reverseTree[elem.id].split('/') : '';
|
||||||
|
for (l = 0, len1 = path.length; l < len1; l++) {
|
||||||
|
node = path[l];
|
||||||
|
if (node.length > 0) {
|
||||||
|
if (offset.length) {
|
||||||
|
found = -1;
|
||||||
|
for (i = m = 0, len2 = offset.length; m < len2; i = ++m) {
|
||||||
|
n = offset[i];
|
||||||
|
if (n.id === node) {
|
||||||
|
found = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found !== -1) {
|
||||||
|
offset = offset[found].nodes;
|
||||||
|
} else {
|
||||||
|
offset.push({
|
||||||
|
id: node,
|
||||||
|
title: $scope.translate(node),
|
||||||
|
nodes: []
|
||||||
|
});
|
||||||
|
offset = offset[offset.length - 1].nodes;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset.push({
|
||||||
|
id: node,
|
||||||
|
title: $scope.translate(node),
|
||||||
|
nodes: []
|
||||||
|
});
|
||||||
|
offset = offset[0].nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset.push(elem);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
$scope.newDiff = function() {
|
||||||
|
return $location.path("/" + $scope.cfg[0].cfgNum + "/" + $scope.cfg[1].cfgNum);
|
||||||
|
};
|
||||||
|
pathEvent = function(event, next, current) {
|
||||||
|
var n;
|
||||||
|
n = next.match(new RegExp('#!?/(latest|[0-9]+)(?:/(latest|[0-9]+))?$'));
|
||||||
|
if (n === null) {
|
||||||
|
$location.path('/latest');
|
||||||
|
} else {
|
||||||
|
$scope.waiting = true;
|
||||||
|
$q.all([
|
||||||
|
$translator.init($scope.lang), $http.get(staticPrefix + "reverseTree.json").then(function(response) {
|
||||||
|
reverseTree = response.data;
|
||||||
|
return console.log("Structure loaded");
|
||||||
|
}), getCfg(0, n[1]), n[2] != null ? getCfg(1, n[2]) : void 0
|
||||||
|
]).then(function() {
|
||||||
|
if (n[2] != null) {
|
||||||
|
return init();
|
||||||
|
} else {
|
||||||
|
if ($scope.cfg[0].prev) {
|
||||||
|
$scope.cfg[1] = $scope.cfg[0];
|
||||||
|
return getCfg(0, $scope.cfg[1].prev).then(function() {
|
||||||
|
return init();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$scope.data = [];
|
||||||
|
return $scope.waiting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function() {
|
||||||
|
$scope.message = $scope.translate('error');
|
||||||
|
return $scope.waiting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
return $scope.$on('$locationChangeSuccess', pathEvent);
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
}).call(this);
|
1
lemonldap-ng-manager/site/htdocs/static/js/viewDiff.min.js
vendored
Normal file
1
lemonldap-ng-manager/site/htdocs/static/js/viewDiff.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(function(){var llapp;llapp=angular.module("llngConfDiff",["ui.tree","ui.bootstrap","llApp","ngCookies"],function($rootScopeProvider){return $rootScopeProvider.digestTtl(15)});llapp.controller("DiffCtrl",["$scope","$http","$q","$translator","$location",function($scope,$http,$q,$translator,$location){var buildTree,getCfg,init,pathEvent,readDiff,reverseTree,toNodes;$scope.links=links;$scope.menulinks=menulinks;$scope.staticPrefix=staticPrefix;$scope.scriptname=scriptname;$scope.availableLanguages=availableLanguages;$scope.waiting=true;$scope.showM=false;$scope.cfg=[];$scope.data={};$scope.currentNode=null;$scope.translateTitle=function(node){return $translator.translateField(node,"title")};$scope.translateP=$translator.translateP;$scope.translate=$translator.translate;$scope.toggle=function(scope){return scope.toggle()};$scope.stoggle=function(scope,node){$scope.currentNode=node;return scope.toggle()};$scope.menuClick=function(button){if(button.popup){window.open(button.popup)}else{if(!button.action){button.action=button.title}switch(typeof button.action){case"function":button.action($scope.currentNode,$scope);break;case"string":$scope[button.action]();break;default:console.log(typeof button.action)}}return $scope.showM=false};$scope.getLanguage=function(lang){$scope.lang=lang;$scope.init();return $scope.showM=false};getCfg=function(b,n){var d;d=$q.defer();if($scope.cfg[b]==null||$scope.cfg[b]!==n){$http.get(""+confPrefix+n).then(function(response){var date;if(response&&response.data){$scope.cfg[b]=response.data;date=new Date(response.data.cfgDate*1e3);$scope.cfg[b].date=date.toLocaleString();console.log("Metadatas of cfg "+n+" loaded");return d.resolve("OK")}else{return d.reject(response)}},function(response){console.log(response);return d.reject("NOK")})}else{d.resolve()}return d.promise};init=function(){var d;$scope.message=null;$scope.currentNode=null;d=$q.defer();return $http.get(scriptname+"view/diff/"+$scope.cfg[0].cfgNum+"/"+$scope.cfg[1].cfgNum).then(function(response){var data;data=[];data=readDiff(response.data[0],response.data[1]);$scope.data=buildTree(data);$scope.message="";return $scope.waiting=false},function(response){return $scope.message=$scope.translate("error")+" : "+response.statusLine})};readDiff=function(c1,c2,tr){var k,res,tmp,v;if(tr==null){tr=true}res=[];for(k in c1){v=c1[k];if(tr){tmp={title:$scope.translate(k),id:k}}else{tmp={title:k}}if(!k.match(/^cfg(?:Num|Log|Author(?:IP)?|Date)$/)){if(v!=null&&typeof v==="object"){if(v.constructor==="array"){tmp.oldvalue=v;tmp.newvalue=c2[k]}else if(typeof c2[k]==="object"){tmp.nodes=readDiff(c1[k],c2[k],false)}else{tmp.oldnodes=toNodes(v,"old")}}else{tmp.oldvalue=v;tmp.newvalue=c2[k]}res.push(tmp)}}for(k in c2){v=c2[k];if(!(k.match(/^cfg(?:Num|Log|Author(?:IP)?|Date)$/)||c1[k]!=null)){if(tr){tmp={title:$scope.translate(k),id:k}}else{tmp={title:k}}if(v!=null&&typeof v==="object"){if(v.constructor==="array"){tmp.newvalue=v}else{console.log("Iteration");tmp.newnodes=toNodes(v,"new")}}else{tmp.newvalue=v}res.push(tmp)}}return res};toNodes=function(c,s){var k,res,tmp,v;res=[];for(k in c){v=c[k];tmp={title:k};if(typeof v==="object"){if(v.constructor==="array"){tmp[s+"value"]=v}else{tmp[s+"nodes"]=toNodes(c[k],s)}}else{tmp[s+"value"]=v}res.push(tmp)}return res};reverseTree=[];buildTree=function(data){var elem,found,i,j,l,len,len1,len2,m,n,node,offset,path,res;if(reverseTree==null){return data}res=[];for(j=0,len=data.length;j<len;j++){elem=data[j];offset=res;path=reverseTree[elem.id]!=null?reverseTree[elem.id].split("/"):"";for(l=0,len1=path.length;l<len1;l++){node=path[l];if(node.length>0){if(offset.length){found=-1;for(i=m=0,len2=offset.length;m<len2;i=++m){n=offset[i];if(n.id===node){found=i}}if(found!==-1){offset=offset[found].nodes}else{offset.push({id:node,title:$scope.translate(node),nodes:[]});offset=offset[offset.length-1].nodes}}else{offset.push({id:node,title:$scope.translate(node),nodes:[]});offset=offset[0].nodes}}}offset.push(elem)}return res};$scope.newDiff=function(){return $location.path("/"+$scope.cfg[0].cfgNum+"/"+$scope.cfg[1].cfgNum)};pathEvent=function(event,next,current){var n;n=next.match(new RegExp("#!?/(latest|[0-9]+)(?:/(latest|[0-9]+))?$"));if(n===null){$location.path("/latest")}else{$scope.waiting=true;$q.all([$translator.init($scope.lang),$http.get(staticPrefix+"reverseTree.json").then(function(response){reverseTree=response.data;return console.log("Structure loaded")}),getCfg(0,n[1]),n[2]!=null?getCfg(1,n[2]):void 0]).then(function(){if(n[2]!=null){return init()}else{if($scope.cfg[0].prev){$scope.cfg[1]=$scope.cfg[0];return getCfg(0,$scope.cfg[1].prev).then(function(){return init()})}else{$scope.data=[];return $scope.waiting=false}}},function(){$scope.message=$scope.translate("error");return $scope.waiting=false})}return true};return $scope.$on("$locationChangeSuccess",pathEvent)}])}).call(this);
|
|
@ -33,9 +33,10 @@ This file contains:
|
||||||
$scope.waiting = true;
|
$scope.waiting = true;
|
||||||
$scope.showM = false;
|
$scope.showM = false;
|
||||||
$scope.showT = false;
|
$scope.showT = false;
|
||||||
$scope.form = 'home';
|
$scope.form = 'homeViewer';
|
||||||
$scope.currentCfg = {};
|
$scope.currentCfg = {};
|
||||||
$scope.viewPrefix = window.viewPrefix;
|
$scope.viewPrefix = window.viewPrefix;
|
||||||
|
$scope.allowDiff = window.allowDiff;
|
||||||
$scope.message = {};
|
$scope.message = {};
|
||||||
$scope.result = '';
|
$scope.result = '';
|
||||||
$scope.translateTitle = function(node) {
|
$scope.translateTitle = function(node) {
|
||||||
|
@ -160,7 +161,7 @@ This file contains:
|
||||||
return $scope.showM = false;
|
return $scope.showM = false;
|
||||||
};
|
};
|
||||||
$scope.home = function() {
|
$scope.home = function() {
|
||||||
$scope.form = 'home';
|
$scope.form = 'homeViewer';
|
||||||
return $scope.showM = false;
|
return $scope.showM = false;
|
||||||
};
|
};
|
||||||
$scope.downloadConf = function() {
|
$scope.downloadConf = function() {
|
||||||
|
@ -528,7 +529,7 @@ This file contains:
|
||||||
};
|
};
|
||||||
$scope.showModal('message.html');
|
$scope.showModal('message.html');
|
||||||
}
|
}
|
||||||
$scope.form = 'home';
|
$scope.form = 'homeViewer';
|
||||||
return $scope.waiting = false;
|
return $scope.waiting = false;
|
||||||
}, readError);
|
}, readError);
|
||||||
$scope.activeModule = "viewer";
|
$scope.activeModule = "viewer";
|
||||||
|
|
File diff suppressed because one or more lines are too long
121
lemonldap-ng-manager/site/templates/viewDiff.tpl
Normal file
121
lemonldap-ng-manager/site/templates/viewDiff.tpl
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<TMPL_INCLUDE NAME="header.tpl">
|
||||||
|
|
||||||
|
<title>LemonLDAP::NG Manager</title>
|
||||||
|
<link rel="prefetch" href="<TMPL_VAR NAME="STATIC_PREFIX">forms/homeViewer.html" />
|
||||||
|
<link rel="prefetch" href="<TMPL_VAR NAME="STATIC_PREFIX">struct.json" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body ng-app="llngConfDiff" ng-controller="DiffCtrl" ng-csp>
|
||||||
|
|
||||||
|
<TMPL_INCLUDE NAME="menubar.tpl">
|
||||||
|
|
||||||
|
<div id="content" class="row container-fluid">
|
||||||
|
<div id="pleaseWait" ng-show="waiting"><span trspan="waitingForDatas"></span></div>
|
||||||
|
|
||||||
|
<!-- Tree -->
|
||||||
|
<aside id="left" class="col-lg-4 col-md-4 col-sm-5 col-xs-12 scrollable " ng-class="{'hidden-xs':!showT}" role="complementary">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<p class="panel-title text-center" trspan="diffViewer"></p>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<a ng-show="cfg[0].prev" class="input-group-addon link glyphicon glyphicon-arrow-left" href="#!/{{cfg[0].prev}}/{{cfg[1].prev}}" role="link"></a>
|
||||||
|
<span class="input-group-addon">1</span>
|
||||||
|
<input class="form-control" size="2" type="integer" ng-model="cfg[0].cfgNum"/>
|
||||||
|
<span class="input-group-addon">2</span>
|
||||||
|
<input class="form-control" size="2" type="integer" ng-model="cfg[1].cfgNum"/>
|
||||||
|
<span class="input-group-addon link glyphicon glyphicon-refresh" ng-click="newDiff()"></span>
|
||||||
|
<a ng-show="cfg[1].next" class="input-group-addon link glyphicon glyphicon-arrow-right" href="#!/{{cfg[0].next}}/{{cfg[1].next}}" role="link"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="region region-sidebar-first">
|
||||||
|
<section id="block-superfish-1" class="block block-superfish clearfix">
|
||||||
|
<div ui-tree data-drag-enabled="false" id="tree-root">
|
||||||
|
<div ng-show="data.length==0" class="center">
|
||||||
|
<span class="label label-warning" trspan="noDatas"></span>
|
||||||
|
</div>
|
||||||
|
<ol ui-tree-nodes="" ng-model="data">
|
||||||
|
<li ng-repeat="node in data" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="hresizer hidden-xs" resizer="vertical" resizer-left="#left" resizer-right="#right"></div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Right div -->
|
||||||
|
<div id="right" class="col-lg-8 col-md-8 col-sm-7 col-xs-12 scrollable" ng-class="{'hidden-xs':showT&&!showM}">
|
||||||
|
<h2 ng-if="message">{{message}}</h2>
|
||||||
|
<div class="panel panel-default" ng-if="currentNode">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">{{currentNode.title}}</h3>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr ng-show="currentNode.oldvalue">
|
||||||
|
<th><span class="old" trspan="oldValue"></span></th>
|
||||||
|
<td id="tdoldarray" ng-show="currentNode.oldvalue.constructor === 'array'">{{currentNode.oldvalue|json}}</td>
|
||||||
|
<td id="tdold" ng-hide="currentNode.oldvalue.constructor === 'array'">{{currentNode.oldvalue}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-show="currentNode.newvalue">
|
||||||
|
<th><span class="new" trspan="newValue"></span></th>
|
||||||
|
<td id="tdnewarray" ng-show="currentNode.newvalue.constructor === 'array'">{{currentNode.newvalue|json}}</td>
|
||||||
|
<td id="tdnew" ng-hide="currentNode.newvalue.constructor === 'array'">{{currentNode.newvalue}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/ng-template" id="nodes_renderer.html">
|
||||||
|
<div ui-tree-handle class="tree-node tree-node-content panel-info tree-node-default">
|
||||||
|
<span ng-include="'arrow.html'"></span>
|
||||||
|
<span id="t-{{node.id}}" ng-click="stoggle(this,node)">{{node.title}}</span>
|
||||||
|
</div>
|
||||||
|
<ol ui-tree-nodes="" ng-model="node" ng-class="{hidden: collapsed}" ng-include="'subnodes.html'">
|
||||||
|
</ol>
|
||||||
|
</script>
|
||||||
|
<script type="text/ng-template" id="newnodes_renderer.html">
|
||||||
|
<div ui-tree-handle class="tree-node tree-node-content panel-info tree-node-default">
|
||||||
|
<span ng-include="'arrow.html'"></span>
|
||||||
|
<span id="t-{{node.id}}" ng-click="stoggle(this,node)" class="new">{{node.title}}</span>
|
||||||
|
</div>
|
||||||
|
<ol ui-tree-nodes="" ng-model="node" ng-class="{hidden: collapsed}" ng-include="'subnodes.html'">
|
||||||
|
</ol>
|
||||||
|
</script>
|
||||||
|
<script type="text/ng-template" id="oldnodes_renderer.html">
|
||||||
|
<div ui-tree-handle class="tree-node tree-node-content panel-info tree-node-default">
|
||||||
|
<span ng-include="'arrow.html'"></span>
|
||||||
|
<span id="t-{{node.id}}" ng-click="stoggle(this,node)" class="old">{{node.title}}</span>
|
||||||
|
</div>
|
||||||
|
<ol ui-tree-nodes="" ng-model="node" ng-class="{hidden: collapsed}" ng-include="'subnodes.html'">
|
||||||
|
</ol>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/ng-template" id="arrow.html">
|
||||||
|
<a class="btn btn-node btn-sm" ng-click="toggle(this)" ng-if="node.nodes||node.newnodes||node.oldnodes">
|
||||||
|
<span class="glyphicon" ng-class="{'glyphicon-chevron-right': collapsed,'glyphicon-chevron-down': !collapsed}"></span>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-node btn-sm" ng-click="toggle(this)" ng-if="node.newvalue||node.oldvalue||node.value">
|
||||||
|
<span class="glyphicon glyphicon-eye-open"></span>
|
||||||
|
</a>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/ng-template" id="subnodes.html">
|
||||||
|
<li ng-repeat="node in node.nodes" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li>
|
||||||
|
<li ng-repeat="node in node.newnodes" ui-tree-node ng-include="'newnodes_renderer.html'" collapsed="true"></li>
|
||||||
|
<li ng-repeat="node in node.oldnodes" ui-tree-node ng-include="'oldnodes_renderer.html'" collapsed="true"></li>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<TMPL_INCLUDE NAME="scripts.tpl">
|
||||||
|
|
||||||
|
<!-- //if:jsminified
|
||||||
|
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/conftree.min.js"></script>
|
||||||
|
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/veiwDiff.min.js"></script>
|
||||||
|
//else -->
|
||||||
|
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/conftree.js"></script>
|
||||||
|
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/viewDiff.js"></script>
|
||||||
|
<!-- //endif -->
|
||||||
|
|
||||||
|
<TMPL_INCLUDE NAME="footer.tpl">
|
|
@ -30,7 +30,7 @@ $count += 4;
|
||||||
|
|
||||||
my %types = %{ getTypes( $tree, values(%$ctrees), $attr ) };
|
my %types = %{ getTypes( $tree, values(%$ctrees), $attr ) };
|
||||||
|
|
||||||
foreach (qw(home menuCat menuApp authParamsTextContainer)) {
|
foreach (qw(home homeViewer menuCat menuApp authParamsTextContainer)) {
|
||||||
ok( $forms{$_}, "Found $_ form" );
|
ok( $forms{$_}, "Found $_ form" );
|
||||||
$count++;
|
$count++;
|
||||||
delete $forms{$_};
|
delete $forms{$_};
|
||||||
|
|
|
@ -7,6 +7,11 @@ use JSON qw(from_json);
|
||||||
|
|
||||||
require 't/test-lib.pm';
|
require 't/test-lib.pm';
|
||||||
|
|
||||||
|
my $struct = 't/jsonfiles/70-diff.json';
|
||||||
|
sub body {
|
||||||
|
return IO::File->new( $struct, 'r' );
|
||||||
|
}
|
||||||
|
|
||||||
# Test that key value is sent
|
# Test that key value is sent
|
||||||
my $res = &client->jsonResponse('/view/1/portalDisplayOidcConsents');
|
my $res = &client->jsonResponse('/view/1/portalDisplayOidcConsents');
|
||||||
ok( $res->{value} eq '$_oidcConnectedRP', 'Key found' );
|
ok( $res->{value} eq '$_oidcConnectedRP', 'Key found' );
|
||||||
|
@ -24,20 +29,31 @@ $res = &client->jsonResponse('/view/latest');
|
||||||
ok( $res->{cfgNum} eq '1', 'Browser is allowed' );
|
ok( $res->{cfgNum} eq '1', 'Browser is allowed' );
|
||||||
count(1);
|
count(1);
|
||||||
|
|
||||||
# Load lemonldap-ng-noBrowser.ini
|
ok( $res = &client->_post( '/confs/', 'cfgNum=1&force=1', &body, 'application/json' ),
|
||||||
use_ok('Lemonldap::NG::Manager::Cli::Lib');
|
"Request succeed" );
|
||||||
my $client2;
|
ok( $res->[0] == 200, "Result code is 200" );
|
||||||
ok(
|
my $resBody;
|
||||||
$client2 = Lemonldap::NG::Manager::Cli::Lib->new(
|
ok( $resBody = from_json( $res->[2]->[0] ), "Result body contains JSON text" );
|
||||||
iniFile => 't/lemonldap-ng-noBrowser.ini'
|
|
||||||
),
|
|
||||||
'Client object'
|
|
||||||
);
|
|
||||||
|
|
||||||
# Try to display latest conf
|
|
||||||
$res = $client2->jsonResponse('/view/1');
|
|
||||||
ok( $res->{value} eq '_Hidden_', 'Browser is NOT allowed' );
|
|
||||||
count(3);
|
count(3);
|
||||||
|
foreach my $i ( 0 .. 1 ) {
|
||||||
|
ok(
|
||||||
|
$resBody->{details}->{__changes__}->[$i]->{key} =~
|
||||||
|
/\b(captcha_login_enabled|captcha_mail_enabled)\b/,
|
||||||
|
"Details with captcha 'login' or 'mail' found"
|
||||||
|
) or print STDERR Dumper($resBody);
|
||||||
|
}
|
||||||
|
count(2);
|
||||||
|
|
||||||
|
# Try to compare confs 1 & 2
|
||||||
|
$res = &client->jsonResponse('/view/diff/1/2');
|
||||||
|
# ok( $res->[1]->{captcha_login_enabled} eq '1', 'Key found' );
|
||||||
|
ok( $res->[1]->{captcha_mail_enabled} eq '0', 'Key found' );
|
||||||
|
ok( 6 == keys %{ $res->[1] }, 'Right number of keys found')
|
||||||
|
or print STDERR Dumper($res);
|
||||||
|
count(2);
|
||||||
|
|
||||||
|
# Remove new conf
|
||||||
|
`rm -rf t/conf/lmConf-2.json`;
|
||||||
|
|
||||||
done_testing( count() );
|
done_testing( count() );
|
||||||
|
|
||||||
|
|
52
lemonldap-ng-manager/t/71-viewer-with-no-diff.t
Normal file
52
lemonldap-ng-manager/t/71-viewer-with-no-diff.t
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Test viewer API
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
use strict;
|
||||||
|
use IO::String;
|
||||||
|
use JSON qw(from_json);
|
||||||
|
|
||||||
|
require 't/test-lib.pm';
|
||||||
|
|
||||||
|
my $struct = 't/jsonfiles/70-diff.json';
|
||||||
|
sub body {
|
||||||
|
return IO::File->new( $struct, 'r' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load lemonldap-ng-noDiff.ini
|
||||||
|
my $client2;
|
||||||
|
ok(
|
||||||
|
$client2 = Lemonldap::NG::Manager::Cli::Lib->new(
|
||||||
|
iniFile => 't/lemonldap-ng-noDiff.ini'
|
||||||
|
),
|
||||||
|
'Client object'
|
||||||
|
);
|
||||||
|
|
||||||
|
# Try to display latest conf
|
||||||
|
my $res = $client2->jsonResponse('/view/1');
|
||||||
|
ok( $res->{value} eq '_Hidden_', 'Browser is NOT allowed' );
|
||||||
|
count(2);
|
||||||
|
|
||||||
|
# Try to compare confs 1 & 2
|
||||||
|
ok( $res = $client2->_post( '/confs/', 'cfgNum=1&force=1', &body, 'application/json' ),
|
||||||
|
"Request succeed" );
|
||||||
|
ok( $res->[0] == 200, "Result code is 200" );
|
||||||
|
my $resBody;
|
||||||
|
ok( $resBody = from_json( $res->[2]->[0] ), "Result body contains JSON text" );
|
||||||
|
count(3);
|
||||||
|
foreach my $i ( 0 .. 1 ) {
|
||||||
|
ok(
|
||||||
|
$resBody->{details}->{__changes__}->[$i]->{key} =~
|
||||||
|
/\b(captcha_login_enabled|captcha_mail_enabled)\b/,
|
||||||
|
"Details with captcha 'login' or 'mail' found"
|
||||||
|
) or print STDERR Dumper($resBody);
|
||||||
|
}
|
||||||
|
count(2);
|
||||||
|
$res = $client2->jsonResponse('/view/diff/1/2');
|
||||||
|
ok( $res->{value} eq '_Hidden_', 'Diff is NOT allowed' );
|
||||||
|
count(1);
|
||||||
|
|
||||||
|
# Remove new conf
|
||||||
|
`rm -rf t/conf/lmConf-2.json`;
|
||||||
|
|
||||||
|
done_testing( count() );
|
||||||
|
|
|
@ -47,8 +47,9 @@ my @notManagedAttributes = (
|
||||||
# Loggers
|
# Loggers
|
||||||
'log4perlConfFile', 'userSyslogFacility', 'logger', 'sentryDsn',
|
'log4perlConfFile', 'userSyslogFacility', 'logger', 'sentryDsn',
|
||||||
'syslogFacility', 'userLogger', 'logLevel',
|
'syslogFacility', 'userLogger', 'logLevel',
|
||||||
|
|
||||||
# Viewer
|
# Viewer
|
||||||
'viewerHiddenPK', 'viewerAllowBrowser',
|
'viewerHiddenKeys', 'viewerAllowBrowser', 'viewerAllowDiff',
|
||||||
|
|
||||||
# Other ini-only prms
|
# Other ini-only prms
|
||||||
'configStorage', 'status', 'localStorageOptions', 'localStorage',
|
'configStorage', 'status', 'localStorageOptions', 'localStorage',
|
||||||
|
|
1797
lemonldap-ng-manager/t/jsonfiles/70-diff.json
Normal file
1797
lemonldap-ng-manager/t/jsonfiles/70-diff.json
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -27,8 +27,9 @@ staticPrefix = app/
|
||||||
languages = fr, en, vi, ar
|
languages = fr, en, vi, ar
|
||||||
templateDir = site/templates/
|
templateDir = site/templates/
|
||||||
enabledModules = conf, sessions, notifications, 2ndFA, viewer
|
enabledModules = conf, sessions, notifications, 2ndFA, viewer
|
||||||
viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout
|
viewerHiddenKeys = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout
|
||||||
viewerAllowBrowser = 0
|
viewerAllowBrowser = 0
|
||||||
|
viewerAllowDiff = 0
|
||||||
|
|
||||||
[sessionsExplorer]
|
[sessionsExplorer]
|
||||||
|
|
|
@ -27,8 +27,9 @@ staticPrefix = app/
|
||||||
languages = fr, en, vi, ar
|
languages = fr, en, vi, ar
|
||||||
templateDir = site/templates/
|
templateDir = site/templates/
|
||||||
enabledModules = conf, sessions, notifications, 2ndFA, viewer
|
enabledModules = conf, sessions, notifications, 2ndFA, viewer
|
||||||
viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout
|
viewerHiddenKeys = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout captcha_login_enabled
|
||||||
viewerAllowBrowser = 1
|
viewerAllowBrowser = 1
|
||||||
|
viewerAllowDiff = 1
|
||||||
|
|
||||||
[sessionsExplorer]
|
[sessionsExplorer]
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,12 @@ sub authenticate {
|
||||||
}
|
}
|
||||||
my $res = $self->p->{_passwordDB}->_modifyPassword( $req, 1 );
|
my $res = $self->p->{_passwordDB}->_modifyPassword( $req, 1 );
|
||||||
|
|
||||||
|
# Refresh entry
|
||||||
|
if ( $self->p->{_userDB}->getUser($req) != PE_OK ) {
|
||||||
|
$self->logger->error(
|
||||||
|
"Unable to refresh entry for " . $self->p->{user} );
|
||||||
|
}
|
||||||
|
|
||||||
# Security: never create session here
|
# Security: never create session here
|
||||||
return $res || PE_DONE;
|
return $res || PE_DONE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,6 @@ qr/^($saml_sso_get_url|$saml_sso_get_url_ret|$saml_sso_post_url|$saml_sso_post_u
|
||||||
'samlAttributeAuthorityDescriptorAttributeServiceSOAP',
|
'samlAttributeAuthorityDescriptorAttributeServiceSOAP',
|
||||||
1, 'attributeServer', ['POST'] );
|
1, 'attributeServer', ['POST'] );
|
||||||
|
|
||||||
# TODO: @coudot, why this URL isn't managed with a conf param ?
|
|
||||||
$self->addUnauthRoute(
|
$self->addUnauthRoute(
|
||||||
$self->path => { relaySingleLogoutSOAP => 'sloRelaySoap' },
|
$self->path => { relaySingleLogoutSOAP => 'sloRelaySoap' },
|
||||||
[ 'GET', 'POST' ]
|
[ 'GET', 'POST' ]
|
||||||
|
@ -156,6 +155,10 @@ qr/^($saml_sso_get_url|$saml_sso_get_url_ret|$saml_sso_post_url|$saml_sso_post_u
|
||||||
$self->path => { relaySingleLogoutPOST => 'sloRelayPost' },
|
$self->path => { relaySingleLogoutPOST => 'sloRelayPost' },
|
||||||
[ 'GET', 'POST' ]
|
[ 'GET', 'POST' ]
|
||||||
);
|
);
|
||||||
|
$self->addUnauthRoute(
|
||||||
|
$self->path => { relaySingleLogoutTermination => 'sloRelayTerm' },
|
||||||
|
[ 'GET', 'POST' ]
|
||||||
|
);
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1412,6 +1415,112 @@ sub sloRelayPost {
|
||||||
return $self->p->do( $req, ['autoPost'] );
|
return $self->p->do( $req, ['autoPost'] );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub sloRelayTerm {
|
||||||
|
my ( $self, $req ) = @_;
|
||||||
|
$self->logger->debug( "URL "
|
||||||
|
. $req->uri
|
||||||
|
. " detected as a SLO Termination relay service URL" );
|
||||||
|
|
||||||
|
# Check if relay parameter is present (mandatory)
|
||||||
|
my $relayID = $self->p->getHiddenFormValue( $req, 'relay', '', 0 )
|
||||||
|
|| $req->param('relay');
|
||||||
|
unless ($relayID) {
|
||||||
|
return $self->p->sendError( $req, 'No relayID detected' );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Retrieve the corresponding data from samlStorage
|
||||||
|
my $relayInfos = $self->getSamlSession($relayID);
|
||||||
|
unless ($relayInfos) {
|
||||||
|
return $self->p->sendError( $req,
|
||||||
|
"Could not get relay session $relayID" );
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->logger->debug("Found relay session $relayID");
|
||||||
|
|
||||||
|
# Get data from relay session
|
||||||
|
my $logout_dump = $relayInfos->data->{_logout};
|
||||||
|
my $session_dump = $relayInfos->data->{_session};
|
||||||
|
my $method = $relayInfos->data->{_method};
|
||||||
|
|
||||||
|
unless ($logout_dump) {
|
||||||
|
$self->logger->error("Could not get logout dump");
|
||||||
|
return PE_SAML_SLO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rebuild Lasso::Logout object
|
||||||
|
my $logout = $self->createLogout( $self->lassoServer, $logout_dump );
|
||||||
|
|
||||||
|
unless ($logout) {
|
||||||
|
$self->logger->error("Could not build Lasso::Logout");
|
||||||
|
return PE_SAML_SLO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Inject session
|
||||||
|
unless ($session_dump) {
|
||||||
|
$self->logger->error("Could not get session dump");
|
||||||
|
return PE_SAML_SLO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
unless ( $self->setSessionFromDump( $logout, $session_dump ) ) {
|
||||||
|
$self->logger->error("Could not set session from dump");
|
||||||
|
return PE_SAML_SLO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get Lasso::Session
|
||||||
|
my $session = $logout->get_session();
|
||||||
|
|
||||||
|
unless ($session) {
|
||||||
|
$self->lmLog( "Could not get session from logout", 'error' );
|
||||||
|
return PE_SAML_SLO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Loop on assertions and remove them if SLO status is OK
|
||||||
|
$self->resetProviderIdIndex($logout);
|
||||||
|
|
||||||
|
while ( my $sp = $self->getNextProviderId($logout) ) {
|
||||||
|
|
||||||
|
# Try to get SLO status from SLO session
|
||||||
|
my $spConfKey = $self->spList->{$sp}->{confKey};
|
||||||
|
my $status = $relayInfos->data->{$spConfKey};
|
||||||
|
|
||||||
|
# Remove assertion if status is OK
|
||||||
|
if ($status) {
|
||||||
|
eval { $session->remove_assertion($sp); };
|
||||||
|
|
||||||
|
if ($@) {
|
||||||
|
$self->logger->warn("Unable to remove assertion for $sp");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->logger->debug("Assertion removed for $sp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->logger->debug(
|
||||||
|
"SLO status was not ok for $sp, assertion not removed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reinject session
|
||||||
|
unless ( $session->is_empty() ) {
|
||||||
|
$self->setSessionFromDump( $logout, $session->dump );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete relay session
|
||||||
|
$relayInfos->remove();
|
||||||
|
|
||||||
|
# Send SLO response
|
||||||
|
if ( my $tmp =
|
||||||
|
$self->sendLogoutResponseToServiceProvider( $req, $logout, $method ) )
|
||||||
|
{
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->logger->error("Fail to send SLO response");
|
||||||
|
return PE_SAML_SLO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
sub authSloServer {
|
sub authSloServer {
|
||||||
my ( $self, $req ) = @_;
|
my ( $self, $req ) = @_;
|
||||||
$self->p->importHandlerData($req);
|
$self->p->importHandlerData($req);
|
||||||
|
@ -1584,6 +1693,8 @@ sub sloServer {
|
||||||
my $sloStatusSessionInfo = $self->getSamlSession( undef, $sloInfos );
|
my $sloStatusSessionInfo = $self->getSamlSession( undef, $sloInfos );
|
||||||
my $relayID = $sloStatusSessionInfo->id;
|
my $relayID = $sloStatusSessionInfo->id;
|
||||||
|
|
||||||
|
$self->logger->debug("Create relay session $relayID");
|
||||||
|
|
||||||
# Prepare logout on all others SP
|
# Prepare logout on all others SP
|
||||||
my $provider_nb =
|
my $provider_nb =
|
||||||
$self->sendLogoutRequestToProviders( $req, $logout, $relayID );
|
$self->sendLogoutRequestToProviders( $req, $logout, $relayID );
|
||||||
|
@ -1620,10 +1731,9 @@ sub sloServer {
|
||||||
else {
|
else {
|
||||||
$req->{urldc} =
|
$req->{urldc} =
|
||||||
$self->conf->{portal} . '/saml/relaySingleLogoutTermination';
|
$self->conf->{portal} . '/saml/relaySingleLogoutTermination';
|
||||||
$self->p->setHiddenFormValue( $req, 'relay', $relayID );
|
$self->p->setHiddenFormValue( $req, 'relay', $relayID, '', 0 );
|
||||||
return $self->p->do( $req, [] );
|
return $self->p->do( $req, [] );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elsif ($response) {
|
elsif ($response) {
|
||||||
|
|
|
@ -207,10 +207,8 @@ sub userBind {
|
||||||
|
|
||||||
# Get expiration warning and graces
|
# Get expiration warning and graces
|
||||||
if ( $resp->grace_authentications_remaining ) {
|
if ( $resp->grace_authentications_remaining ) {
|
||||||
|
$req->info(
|
||||||
# TODO
|
$self->{portal}->loadTemplate(
|
||||||
$self->info(
|
|
||||||
$self->loadTemplate(
|
|
||||||
'ldapPpGrace',
|
'ldapPpGrace',
|
||||||
params => {
|
params => {
|
||||||
number => $resp->grace_authentications_remaining
|
number => $resp->grace_authentications_remaining
|
||||||
|
@ -221,7 +219,7 @@ sub userBind {
|
||||||
|
|
||||||
if ( $resp->time_before_expiration ) {
|
if ( $resp->time_before_expiration ) {
|
||||||
$req->info(
|
$req->info(
|
||||||
$self->loadTemplate(
|
$self->{portal}->loadTemplate(
|
||||||
'simpleInfo',
|
'simpleInfo',
|
||||||
params => {
|
params => {
|
||||||
trspan => 'authRemaining,'
|
trspan => 'authRemaining,'
|
||||||
|
|
|
@ -247,6 +247,8 @@ sub do {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (
|
if (
|
||||||
|
$req->info
|
||||||
|
or (
|
||||||
$err
|
$err
|
||||||
and $err != PE_LOGOUT_OK
|
and $err != PE_LOGOUT_OK
|
||||||
and (
|
and (
|
||||||
|
@ -254,7 +256,7 @@ sub do {
|
||||||
or ( $err == PE_REDIRECT
|
or ( $err == PE_REDIRECT
|
||||||
and $req->data->{redirectFormMethod}
|
and $req->data->{redirectFormMethod}
|
||||||
and $req->data->{redirectFormMethod} eq 'post' )
|
and $req->data->{redirectFormMethod} eq 'post' )
|
||||||
or $req->info
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -560,7 +562,10 @@ sub _deleteSession {
|
||||||
|
|
||||||
# Log
|
# Log
|
||||||
my $user = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
|
my $user = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
|
||||||
$self->userLogger->notice("User $user has been disconnected") if $user;
|
my $mod = $req->{sessionInfo}->{_auth};
|
||||||
|
$self->userLogger->notice(
|
||||||
|
"User $user has been disconnected from $mod ($req->{sessionInfo}->{ipAddr})"
|
||||||
|
) if $user;
|
||||||
|
|
||||||
return $session->error ? 0 : 1;
|
return $session->error ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
@ -828,7 +833,7 @@ sub sendHtml {
|
||||||
( $req->info =~ /<iframe.*?src="(.*?)"/sg );
|
( $req->info =~ /<iframe.*?src="(.*?)"/sg );
|
||||||
}
|
}
|
||||||
if (@url) {
|
if (@url) {
|
||||||
$csp .= join( ' ', 'child-src', @url ) . ';';
|
$csp .= join( ' ', 'child-src', @url, "'self'" ) . ';';
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set CSP header
|
# Set CSP header
|
||||||
|
|
|
@ -92,6 +92,13 @@ sub run {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Log
|
||||||
|
my $user = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
|
||||||
|
my $mod = $req->{sessionInfo}->{_auth};
|
||||||
|
$self->userLogger->notice(
|
||||||
|
"Session granted for $user by $mod ($req->{sessionInfo}->{ipAddr})")
|
||||||
|
if $user;
|
||||||
return PE_OK;
|
return PE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ require 't/test-lib.pm';
|
||||||
use lib 't/lib';
|
use lib 't/lib';
|
||||||
|
|
||||||
my $res;
|
my $res;
|
||||||
my $maintests = 22;
|
my $maintests = 24;
|
||||||
|
|
||||||
SKIP: {
|
SKIP: {
|
||||||
skip( 'LLNGTESTLDAP is not set', $maintests ) unless ( $ENV{LLNGTESTLDAP} );
|
skip( 'LLNGTESTLDAP is not set', $maintests ) unless ( $ENV{LLNGTESTLDAP} );
|
||||||
|
@ -33,7 +33,7 @@ SKIP: {
|
||||||
);
|
);
|
||||||
use Lemonldap::NG::Portal::Main::Constants 'PE_PP_CHANGE_AFTER_RESET',
|
use Lemonldap::NG::Portal::Main::Constants 'PE_PP_CHANGE_AFTER_RESET',
|
||||||
'PE_PP_PASSWORD_EXPIRED', 'PE_PASSWORD_OK', 'PE_PP_ACCOUNT_LOCKED',
|
'PE_PP_PASSWORD_EXPIRED', 'PE_PASSWORD_OK', 'PE_PP_ACCOUNT_LOCKED',
|
||||||
'PE_PP_PASSWORD_TOO_SHORT';
|
'PE_PP_PASSWORD_TOO_SHORT', 'PE_PP_GRACE';
|
||||||
|
|
||||||
# 1 - TEST PE_PP_CHANGE_AFTER_RESET AND PE_PP_PASSWORD_EXPIRED
|
# 1 - TEST PE_PP_CHANGE_AFTER_RESET AND PE_PP_PASSWORD_EXPIRED
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
@ -46,7 +46,7 @@ SKIP: {
|
||||||
my $code = $tpl->[1];
|
my $code = $tpl->[1];
|
||||||
my $postString = "user=$user&password=$user";
|
my $postString = "user=$user&password=$user";
|
||||||
|
|
||||||
# Try yo authenticate
|
# Try to authenticate
|
||||||
# -------------------
|
# -------------------
|
||||||
ok(
|
ok(
|
||||||
$res = $client->_post(
|
$res = $client->_post(
|
||||||
|
@ -91,13 +91,32 @@ SKIP: {
|
||||||
expectCookie($res) or print STDERR Dumper($res);
|
expectCookie($res) or print STDERR Dumper($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2 - TEST PE_PP_ACCOUNT_LOCKED
|
# 2 - TEST PE_PP_GRACE
|
||||||
|
# -------------------------
|
||||||
|
my $user = 'grace';
|
||||||
|
my $code = "ppGrace";
|
||||||
|
my $postString = "user=$user&password=$user";
|
||||||
|
|
||||||
|
# Try to authenticate
|
||||||
|
# -------------------
|
||||||
|
ok(
|
||||||
|
$res = $client->_post(
|
||||||
|
'/', IO::String->new($postString),
|
||||||
|
length => length($postString),
|
||||||
|
accept => 'text/html',
|
||||||
|
),
|
||||||
|
'Auth query'
|
||||||
|
);
|
||||||
|
my $match = 'trmsg="' . $code . '"';
|
||||||
|
ok( $res->[2]->[0] =~ /$match/, 'Grace remaining' );
|
||||||
|
|
||||||
|
# 3 - TEST PE_PP_ACCOUNT_LOCKED
|
||||||
# -------------------------
|
# -------------------------
|
||||||
my $user = 'lock';
|
my $user = 'lock';
|
||||||
my $code = PE_PP_ACCOUNT_LOCKED;
|
my $code = PE_PP_ACCOUNT_LOCKED;
|
||||||
my $postString = "user=$user&password=$user";
|
my $postString = "user=$user&password=$user";
|
||||||
|
|
||||||
# Try yo authenticate
|
# Try to authenticate
|
||||||
# -------------------
|
# -------------------
|
||||||
ok(
|
ok(
|
||||||
$res = $client->_post(
|
$res = $client->_post(
|
||||||
|
@ -124,13 +143,13 @@ SKIP: {
|
||||||
$match = 'trmsg="' . PE_PASSWORD_OK . '"';
|
$match = 'trmsg="' . PE_PASSWORD_OK . '"';
|
||||||
ok( $res->[2]->[0] !~ /$match/s, 'Password is not changed' );
|
ok( $res->[2]->[0] !~ /$match/s, 'Password is not changed' );
|
||||||
|
|
||||||
# 3 - TEST PE_PP_PASSWORD_TOO_SHORT
|
# 4 - TEST PE_PP_PASSWORD_TOO_SHORT
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
$user = 'short';
|
$user = 'short';
|
||||||
$code = PE_PP_PASSWORD_TOO_SHORT;
|
$code = PE_PP_PASSWORD_TOO_SHORT;
|
||||||
$postString = "user=$user&password=passwordnottooshort";
|
$postString = "user=$user&password=passwordnottooshort";
|
||||||
|
|
||||||
# Try yo authenticate
|
# Try to authenticate
|
||||||
# -------------------
|
# -------------------
|
||||||
ok(
|
ok(
|
||||||
$res = $client->_post(
|
$res = $client->_post(
|
||||||
|
|
|
@ -86,6 +86,16 @@ mail: short@badwolf.org
|
||||||
userPassword: passwordnottooshort
|
userPassword: passwordnottooshort
|
||||||
pwdPolicySubentry: cn=passwordshort,ou=ppolicies,dc=example,dc=com
|
pwdPolicySubentry: cn=passwordshort,ou=ppolicies,dc=example,dc=com
|
||||||
|
|
||||||
|
dn: uid=grace,ou=users,dc=example,dc=com
|
||||||
|
objectClass: inetOrgPerson
|
||||||
|
uid: grace
|
||||||
|
cn: grace
|
||||||
|
sn: grace
|
||||||
|
mail: grace@badwolf.org
|
||||||
|
userPassword: grace
|
||||||
|
pwdPolicySubentry: cn=passwordgrace,ou=ppolicies,dc=example,dc=com
|
||||||
|
pwdChangedTime: 20190101000000Z
|
||||||
|
|
||||||
dn: ou=ppolicies,dc=example,dc=com
|
dn: ou=ppolicies,dc=example,dc=com
|
||||||
objectClass: top
|
objectClass: top
|
||||||
objectClass: organizationalUnit
|
objectClass: organizationalUnit
|
||||||
|
@ -128,3 +138,13 @@ pwdAllowUserChange: TRUE
|
||||||
pwdCheckQuality: 2
|
pwdCheckQuality: 2
|
||||||
pwdMinLength: 6
|
pwdMinLength: 6
|
||||||
|
|
||||||
|
dn: cn=passwordgrace,ou=ppolicies,dc=example,dc=com
|
||||||
|
objectClass: device
|
||||||
|
objectClass: pwdPolicy
|
||||||
|
cn: passwordgrace
|
||||||
|
pwdAttribute: userPassword
|
||||||
|
pwdAllowUserChange: TRUE
|
||||||
|
pwdCheckQuality: 0
|
||||||
|
pwdMaxAge: 5
|
||||||
|
pwdGraceAuthnLimit: 2
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user