Commit fb7541ac authored by Christophe Maudoux's avatar Christophe Maudoux 🐛

Merge branch 'viewer' into 'v2.0'

Viewer

See merge request lemonldap-ng/lemonldap-ng!67
parents b7a78b50 eba7a48f
......@@ -119,6 +119,11 @@
"namespace" : "lemonldap-ng-sessions"
},
"locationRules" : {
"auth.example.com" : {
"(?#checkUser)^/checkuser": "$uid eq \"dwho\"",
"(?#errors)^/lmerror/": "accept",
"default" : "accept"
},
"manager.__DNSDOMAIN__" : {
"(?#Configuration)^/(manager\\.html|conf/)" : "$uid eq \"dwho\"",
"(?#Notifications)/notifications" : "$uid eq \"dwho\" or $uid eq \"rtyler\"",
......
......@@ -38,7 +38,7 @@ useRedirectOnError = 0
[manager]
enabledModules = conf, sessions, notifications, 2ndFA
enabledModules = conf, sessions, notifications, 2ndFA, viewer
protection = manager
staticPrefix = /static
languages = fr, en, vi, ar, de, it, zh
......
......@@ -296,13 +296,14 @@ sub defaultValues {
'useRedirectOnError' => 1,
'useSafeJail' => 1,
'utotp2fActivation' => 0,
'webIDAuthnLevel' => 1,
'webIDExportedVars' => {},
'whatToTrace' => 'uid',
'yubikey2fActivation' => 0,
'yubikey2fPublicIDSize' => 12,
'yubikey2fSelfRegistration' => 0,
'yubikey2fUserCanRemoveKey' => 1
'viewerHiddenPK' => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
'webIDAuthnLevel' => 1,
'webIDExportedVars' => {},
'whatToTrace' => 'uid',
'yubikey2fActivation' => 0,
'yubikey2fPublicIDSize' => 12,
'yubikey2fSelfRegistration' => 0,
'yubikey2fUserCanRemoveKey' => 1
};
}
......
......@@ -23,6 +23,7 @@ extends 'Lemonldap::NG::Common::Conf::AccessLib',
'Lemonldap::NG::Handler::PSGI::Router';
has csp => ( is => 'rw' );
has brw => ( is => 'rw', default => 0 );
## @method boolean init($args)
# Launch initialization method
......@@ -86,7 +87,7 @@ sub init {
$self->csp(
"default-src 'self' $portal;frame-ancestors 'none';form-action 'self';"
);
$self->brw( $conf->{viewerAllowBrowser} );
$self->defaultRoute( $working[0]->defaultRoute );
# Find out more glyphicones at https://www.w3schools.com/icons/bootstrap_icons_glyphicons.asp
......@@ -94,7 +95,8 @@ sub init {
'conf' => 'cog',
'sessions' => 'duplicate',
'notifications' => 'bell',
'2ndFA' => 'wrench'
'2ndFA' => 'wrench',
'viewer' => 'eye-open',
};
$self->links( [] );
......@@ -132,13 +134,14 @@ sub init {
}
sub tplParams {
return ( VERSION => $VERSION, );
my ($self) = @_;
return ( VERSION => $VERSION, ALLOWBROWSER => $self->brw );
}
sub javascript {
my ($self) = @_;
return
'var formPrefix=staticPrefix+"forms/";var confPrefix=scriptname+"confs/";'
'var formPrefix=staticPrefix+"forms/";var confPrefix=scriptname+"confs/";var viewPrefix=scriptname+"view/";'
. ( $self->links ? 'var links=' . to_json( $self->links ) . ';' : '' )
. (
$self->menuLinks
......
......@@ -3558,6 +3558,14 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
],
'type' => 'select'
},
'viewerAllowBrowser' => {
'default' => 0,
'type' => 'bool'
},
'viewerHiddenPK' => {
'default' => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
'type' => 'text'
},
'virtualHosts' => {
'type' => 'virtualHostContainer'
},
......
......@@ -915,6 +915,19 @@ sub attributes {
flags => 'hp',
},
# Viewer
viewerHiddenPK => {
type => 'text',
default => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
documentation => 'ConfTree hidden primary keys',
flags => 'm',
},
viewerAllowBrowser => {
type => 'bool',
default => 0,
documentation => 'Allow configuration browser',
},
# Notification
oldNotifFormat => {
type => 'bool',
......
# This module implements all the methods that responds to '/confs/*' requests
# It contains 2 sections:
# - initialization methods
# - upload method
#
# Read methods are inherited from Lemonldap::NG::Common::Conf::RESTServer
package Lemonldap::NG::Manager::Viewer;
use 5.10.0;
use utf8;
use Mouse;
use Lemonldap::NG::Common::Conf::Constants;
use Lemonldap::NG::Common::UserAgent;
use URI::URL;
use feature 'state';
extends 'Lemonldap::NG::Manager::Conf';
our $VERSION = '2.0.3';
#############################
# I. INITIALIZATION METHODS #
#############################
use constant defaultRoute => 'viewer.html';
has ua => ( is => 'rw' );
sub addRoutes {
my ( $self, $conf ) = @_;
$self->ua( Lemonldap::NG::Common::UserAgent->new($conf) );
my @enabledPK = ();
my @keys = qw(virtualHosts samlIDPMetaDataNodes samlSPMetaDataNodes
applicationList oidcOPMetaDataNodes oidcRPMetaDataNodes
casSrvMetaDataNodes casAppMetaDataNodes
authChoiceModules grantSessionRules combModules
openIdIDPList);
foreach (@keys) {
# Ignore hidden ConfTree Primary Keys
push @enabledPK, $_
unless ( $conf->{viewerHiddenPK} =~ /\b$_\b/ );
}
# HTML template
$self->addRoute( 'viewer.html', undef, ['GET'] )
# READ
# Special keys
->addRoute(
view => {
':cfgNum' => \@enabledPK
},
['GET']
)
# Other keys
->addRoute( view => { ':cfgNum' => { '*' => 'getKey' } }, ['GET'] )
# Difference between confs
->addRoute( diff => { ':conf1' => { ':conf2' => 'diff' } } )
->addRoute( 'diff.html', undef, ['GET'] );
}
sub getConfByNum {
my ( $self, $cfgNum, @args ) = @_;
$self->SUPER::getConfByNum( $cfgNum, @args );
}
sub diff {
my ( $self, $req, @path ) = @_;
$self->SUPER::diff( $req, @path );
}
1;
This diff is collapsed.
<div class="panel panel-default">
<div class="panel-heading">
<TMPL_IF NAME="ALLOWDIFF">
<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">(<a trspan="diffWithPrevious" target="_blank" href="{{scriptname}}/diff.html#!/{{currentCfg.cfgNum}}" role="link"></a>)</i>
</h3>
</TMPL_IF>
</div>
<table class="table table-striped">
<tr>
......
This diff is collapsed.
(function(){var llapp;llapp=angular.module("llngManager",["ui.tree","ui.bootstrap","llApp","ngCookies"]);llapp.controller("TreeCtrl",["$scope","$http","$location","$q","$uibModal","$translator","$cookies","$htmlParams",function($scope,$http,$location,$q,$uibModal,$translator,$cookies,$htmlParams){var _download,_getAll,_stoggle,c,id,pathEvent,readError,setHelp;$scope.links=window.links;$scope.menu=$htmlParams.menu;$scope.menulinks=window.menulinks;$scope.staticPrefix=window.staticPrefix;$scope.formPrefix=window.formPrefix;$scope.availableLanguages=window.availableLanguages;$scope.waiting=true;$scope.showM=false;$scope.showT=false;$scope.form="home";$scope.currentCfg={};$scope.viewPrefix=window.viewPrefix;$scope.message={};$scope.result="";$scope.translateTitle=function(node){return $translator.translateField(node,"title")};$scope.translateP=$translator.translateP;$scope.translate=$translator.translate;$scope.helpUrl="start.html#configuration";$scope.setShowHelp=function(val){var d;if(val==null){val=!$scope.showH}$scope.showH=val;d=new Date(Date.now());d.setFullYear(d.getFullYear()+1);return $cookies.put("showhelp",val?"true":"false",{expires:d})};$scope.showH=$cookies.get("showhelp")==="false"?false:true;if($scope.showH==null){$scope.setShowHelp(true)}readError=function(response){var e,j;e=response.status;j=response.statusLine;$scope.waiting=false;if(e===403){$scope.message={title:"forbidden",message:"",items:[]}}else if(e===401){console.log("Authentication needed");$scope.message={title:"authenticationNeeded",message:"__waitOrF5__",items:[]}}else if(e===400){$scope.message={title:"badRequest",message:j,items:[]}}else if(e>0){$scope.message={title:"badRequest",message:j,items:[]}}else{$scope.message={title:"networkProblem",message:"",items:[]}}return $scope.showModal("message.html")};$scope.showModal=function(tpl,init){var d,modalInstance;modalInstance=$uibModal.open({templateUrl:tpl,controller:"ModalInstanceCtrl",size:"lg",resolve:{elem:function(){return function(s){return $scope[s]}},set:function(){return function(f,s){return $scope[f]=s}},init:function(){return init}}});d=$q.defer();modalInstance.result.then(function(msgok){$scope.message={title:"",message:"",items:[]};return d.resolve(msgok)},function(msgnok){$scope.message={title:"",message:"",items:[]};return d.reject(msgnok)});return d.promise};$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.home=function(){$scope.form="home";return $scope.showM=false};$scope.downloadConf=function(){return window.open($scope.viewPrefix+$scope.currentCfg.cfgNum+"?full=1")};id=1;$scope._findContainer=function(){return $scope._findScopeContainer().$modelValue};$scope._findScopeContainer=function(){var cs;cs=$scope.currentScope;while(!cs.$modelValue.type.match(/Container$/)){cs=cs.$parentNodeScope}return cs};$scope._findScopeByKey=function(k){var cs;cs=$scope.currentScope;while(!(cs.$modelValue.title===k)){cs=cs.$parentNodeScope}return cs};_getAll=function(node){var d,d2;d=$q.defer();d2=$q.defer();if(node._nodes){_stoggle(node);d.resolve()}else if(node.cnodes){_download(node).then(function(){return d.resolve()})}else if(node.nodes||node.data){d.resolve()}else{$scope.getKey(node).then(function(){return d.resolve()})}d.promise.then(function(){var l,len,n,ref,t;t=[];if(node.nodes){ref=node.nodes;for(l=0,len=ref.length;l<len;l++){n=ref[l];t.push(_getAll(n))}}return $q.all(t).then(function(){return d2.resolve()})});return d2.promise};$scope.down=function(){var i,ind,l,len,n,p,ref,tmp;id=$scope.currentNode.id;p=$scope.currentScope.$parentNodeScope.$modelValue;ind=p.nodes.length;ref=p.nodes;for(i=l=0,len=ref.length;l<len;i=++l){n=ref[i];if(n.id===id){ind=i}}if(ind<p.nodes.length-1){tmp=p.nodes[ind];p.nodes[ind]=p.nodes[ind+1];p.nodes[ind+1]=tmp}return ind};$scope.up=function(){var i,ind,l,len,n,p,ref,tmp;id=$scope.currentNode.id;p=$scope.currentScope.$parentNodeScope.$modelValue;ind=-1;ref=p.nodes;for(i=l=0,len=ref.length;l<len;i=++l){n=ref[i];if(n.id===id){ind=i}}if(ind>0){tmp=p.nodes[ind];p.nodes[ind]=p.nodes[ind-1];p.nodes[ind-1]=tmp}return ind};$scope.inSelect=function(value){var l,len,n,ref;ref=$scope.currentNode.select;for(l=0,len=ref.length;l<len;l++){n=ref[l];if(n.k===value){return true}}return false};$scope.changeRuleTitle=function(node){return node.title=node.comment.length>0?node.comment:node.re};$scope.filters={};$scope.execFilters=function(scope){var filter,func,ref;scope=scope?scope:$scope;ref=$scope.filters;for(filter in ref){func=ref[filter];if($scope.filters.hasOwnProperty(filter)){return window.filterFunctions[filter](scope,$q,func)}}return false};$scope.stoggle=function(scope){var node;node=scope.$modelValue;_stoggle(node);return scope.toggle()};_stoggle=function(node){var a,l,len,len1,len2,m,n,o,ref,ref1,ref2;ref=["nodes","nodes_cond"];for(l=0,len=ref.length;l<len;l++){n=ref[l];if(node["_"+n]){node[n]=[];ref1=node["_"+n];for(m=0,len1=ref1.length;m<len1;m++){a=ref1[m];node[n].push(a)}delete node["_"+n]}}if(node._nodes_filter){if(node.nodes){ref2=node.nodes;for(o=0,len2=ref2.length;o<len2;o++){n=ref2[o];n.onChange=$scope.execFilters}}$scope.filters[node._nodes_filter]=node;return $scope.execFilters()}};$scope.toggle=function(scope){return scope.toggle()};$scope.download=function(scope){var node;node=scope.$modelValue;return _download(node)};_download=function(node){var d;d=$q.defer();d.notify("Trying to get datas");$scope.waiting=true;$http.get(""+window.viewPrefix+$scope.currentCfg.cfgNum+"/"+node.cnodes).then(function(response){var a,data,l,len;data=response.data;if(!data){d.reject("Empty response from server")}else if(data.error){if(data.error.match(/setDefault$/)){if(node["default"]){node.nodes=node["default"].slice(0)}else{node.nodes=[]}delete node.cnodes;d.resolve("Set data to default value")}else{d.reject("Server return an error: "+data.error)}}else{delete node.cnodes;if(!node.type){node.type="keyTextContainer"}node.nodes=[];for(l=0,len=data.length;l<len;l++){a=data[l];if(a.template){a._nodes=templates(a.template,a.title)}node.nodes.push(a)}d.resolve("OK")}return $scope.waiting=false},function(response){readError(response);return d.reject("")});return d.promise};$scope.openCnode=function(scope){return $scope.download(scope).then(function(){return scope.toggle()})};setHelp=function(scope){while(!scope.$modelValue.help&&scope.$parentNodeScope){scope=scope.$parentNodeScope}return $scope.helpUrl=scope.$modelValue.help||"start.html#configuration"};$scope.displayForm=function(scope){var f,l,len,n,node,ref;node=scope.$modelValue;if(node.cnodes){$scope.download(scope)}if(node._nodes){$scope.stoggle(scope)}$scope.currentNode=node;$scope.currentScope=scope;f=node.type?node.type:"text";if(node.nodes||node._nodes||node.cnodes){$scope.form=f!=="text"?f:"mini"}else{$scope.form=f;$scope.getKey(node)}if(node.type&&node.type==="simpleInputContainer"){ref=node.nodes;for(l=0,len=ref.length;l<len;l++){n=ref[l];$scope.getKey(n)}}$scope.showT=false;return setHelp(scope)};$scope.getKey=function(node){var d,i,l,len,n,ref,tmp;d=$q.defer();if(!node.data){$scope.waiting=true;if(node.get&&typeof node.get==="object"){node.data=[];tmp=[];ref=node.get;for(i=l=0,len=ref.length;l<len;i=++l){n=ref[i];node.data[i]={title:n,id:n};tmp.push($scope.getKey(node.data[i]))}$q.all(tmp).then(function(){return d.resolve(node.data)},function(response){d.reject(response.statusLine);return $scope.waiting=false})}else{$http.get(""+window.viewPrefix+$scope.currentCfg.cfgNum+"/"+(node.get?node.get:node.title)).then(function(response){var data;data=response.data;if((data.value===null||data.error&&data.error.match(/setDefault$/))&&node["default"]!==null){node.data=node["default"]}else{node.data=data.value}if(node.type&&node.type.match(/^int$/)){node.data=parseInt(node.data,10)}else if(node.type&&node.type.match(/^(saml(Service|Assertion)|blackWhiteList)$/)&&!(typeof node.data==="object")){node.data=node.data.split(";")}$scope.waiting=false;return d.resolve(node.data)},function(response){readError(response);return d.reject(response.status)})}}else{d.resolve(node.data)}return d.promise};pathEvent=function(event,next,current){var n;n=next.match(new RegExp("#!?/view/(latest|[0-9]+)"));if(n===null){return $location.path("/view/latest")}else{console.log("Trying to get cfg number "+n[1]);return $scope.getCfg(n[1])}};$scope.$on("$locationChangeSuccess",pathEvent);$scope.getCfg=function(n){if($scope.currentCfg.cfgNum!==n){return $http.get(""+window.viewPrefix+n).then(function(response){var d;$scope.currentCfg=response.data;d=new Date($scope.currentCfg.cfgDate*1e3);$scope.currentCfg.date=d.toLocaleString();console.log("Metadatas of cfg "+n+" loaded");$location.path("/view/"+n);return $scope.init()},function(response){return readError(response).then(function(){$scope.currentCfg.cfgNum=0;return $scope.init()})})}else{return $scope.waiting=false}};$scope.getLanguage=function(lang){$scope.lang=lang;$scope.form="white";$scope.init();return $scope.showM=false};$scope.init=function(){var tmp;tmp=null;$scope.waiting=true;$scope.data=[];$scope.confirmNeeded=false;$scope.forceSave=false;$q.all([$translator.init($scope.lang),$http.get(window.staticPrefix+"struct.json").then(function(response){tmp=response.data;return console.log("Structure loaded")})]).then(function(){console.log("Starting structure binding");$scope.data=tmp;tmp=null;if($scope.currentCfg.cfgNum!==0){setScopeVars($scope)}else{$scope.message={title:"emptyConf",message:"__zeroConfExplanations__"};$scope.showModal("message.html")}$scope.form="home";return $scope.waiting=false},readError);$scope.activeModule="viewer";return $scope.myStyle={color:"#ffb84d"}};c=$location.path().match(new RegExp("^/view/(latest|[0-9]+)"));if(!c){console.log("Redirecting to /view/latest");return $location.path("/view/latest")}}])}).call(this);
......@@ -648,6 +648,7 @@
"radiusSecret":"سر مشترك",
"radiusServer":"اسم الخادم",
"randomPasswordRegexp":"التعبير النمطي لتوليد كلمة المرور",
"readOnlyMode":"Read-Only mode",
"redirectFormMethod":"طريقة إعادة توجيه الإستمارة",
"redirection":"معالج إعادة التوجيه",
"reference":"مرجع",
......@@ -819,6 +820,7 @@
"vhostPort":"المنفذ",
"vhostType":"نوع",
"view":"عرض",
"viewer":"Viewer",
"virtualHost":"المضيف الإفتراضى ",
"virtualHostName":"اسم المضيف الافتراضي",
"virtualHosts":"المضيفين الإفتراضيين",
......
......@@ -648,6 +648,7 @@
"radiusSecret":"Shared secret",
"radiusServer":"Server hostname",
"randomPasswordRegexp":"Regexp for password generation",
"readOnlyMode":"Read-Only mode",
"redirectFormMethod":"Method for redirect form",
"redirection":"Handler redirections",
"reference":"Reference",
......@@ -819,6 +820,7 @@
"vhostPort":"Port",
"vhostType":"Type",
"view":"View",
"viewer":"Viewer",
"virtualHost":"Virtual Host",
"virtualHostName":"Virtual host hostname",
"virtualHosts":"Virtual Hosts",
......
......@@ -648,6 +648,7 @@
"radiusSecret":"Shared secret",
"radiusServer":"Server hostname",
"randomPasswordRegexp":"Regexp for password generation",
"readOnlyMode":"Read-Only mode",
"redirectFormMethod":"Method for redirect form",
"redirection":"Handler redirections",
"reference":"Reference",
......@@ -819,6 +820,7 @@
"vhostPort":"Port",
"vhostType":"Type",
"view":"View",
"viewer":"Viewer",
"virtualHost":"Virtual Host",
"virtualHostName":"Virtual host hostname",
"virtualHosts":"Virtual Hosts",
......
......@@ -648,6 +648,7 @@
"radiusSecret":"Secret partagé",
"radiusServer":"Nom d'hôte du serveur",
"randomPasswordRegexp":"Expression regulière pour la génération des mots de passe",
"readOnlyMode":"Mode lecture seule",
"redirectFormMethod":"Méthode du formulaire de redirection",
"redirection":"Redirections du Handler",
"reference":"Référence",
......@@ -819,6 +820,7 @@
"vhostPort":"Port",
"vhostType":"Type",
"view":"Aperçu",
"viewer":"Explorateur",
"virtualHost":"Hôte virtuel",
"virtualHostName":"Nom de l'hôte virtuel",
"virtualHosts":"Hôtes virtuels",
......
......@@ -648,6 +648,7 @@
"radiusSecret":"Segreto condiviso",
"radiusServer":"Nome host del server",
"randomPasswordRegexp":"Regex per la generazione di password",
"readOnlyMode":"Read-Only mode",
"redirectFormMethod":"Metodo per il modulo di reindirizzamento",
"redirection":"Redirezioni del gestore",
"reference":"Riferimento",
......@@ -819,6 +820,7 @@
"vhostPort":"Porta",
"vhostType":"Typo",
"view":"Visualizzazione",
"viewer":"Viewer",
"virtualHost":"Virtual Host",
"virtualHostName":"Hostname di Virtual host",
"virtualHosts":"Virtual Hosts",
......
......@@ -648,6 +648,7 @@
"radiusSecret":"Bí mật đã được chia sẻ",
"radiusServer":"Máy chủ lưu trữ",
"randomPasswordRegexp":"Regexp để tạo mật khẩu",
"readOnlyMode":"Read-Only mode",
"redirectFormMethod":"Phương pháp chuyển hướng mẫu",
"redirection":"chuyển hướng trình điều khiển",
"reference":"Tham khảo",
......@@ -819,6 +820,7 @@
"vhostPort":"Port",
"vhostType":"Loại",
"view":"Khung nhìn",
"viewer":"Viewer",
"virtualHost":"Máy chủ ảo",
"virtualHostName":"Tên máy chủ lưu trữ ảo",
"virtualHosts":"Máy chủ ảo",
......
......@@ -648,6 +648,7 @@
"radiusSecret":"Shared secret",
"radiusServer":"Server hostname",
"randomPasswordRegexp":"Regexp for password generation",
"readOnlyMode":"Read-Only mode",
"redirectFormMethod":"Method for redirect form",
"redirection":"Handler redirections",
"reference":"Reference",
......@@ -819,6 +820,7 @@
"vhostPort":"Port",
"vhostType":"Type",
"view":"View",
"viewer":"Viewer",
"virtualHost":"Virtual Host",
"virtualHostName":"Virtual host hostname",
"virtualHosts":"Virtual Hosts",
......
This diff is collapsed.
......@@ -47,6 +47,8 @@ my @notManagedAttributes = (
# Loggers
'log4perlConfFile', 'userSyslogFacility', 'logger', 'sentryDsn',
'syslogFacility', 'userLogger', 'logLevel',
# Viewer
'viewerHiddenPK', 'viewerAllowBrowser',
# Other ini-only prms
'configStorage', 'status', 'localStorageOptions', 'localStorage',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment