Merge branch 'v2.0'

This commit is contained in:
Xavier 2019-03-19 08:32:33 +01:00
commit 4fcf77e721
37 changed files with 1447 additions and 27 deletions

View File

@ -15,3 +15,4 @@
--noopening-brace-on-new-line
--stack-opening-tokens
--format-skipping
--continuation-indentation=2

View File

@ -1046,10 +1046,11 @@ zip-dist:
manifest: configure
@for i in ${SRCCOMMONDIR} ${SRCHANDLERDIR} ${SRCPORTALDIR} ${SRCMANAGERDIR}; do \
cd $$i; \
rm -vf MANIFEST*; \
rm -vf MANIFEST MANIFEST*bak; \
make manifest; \
cd -; \
done
perl -i -ne 'print unless/proverc/' */MANIFEST
cpan: clean configure common_cpan handler_cpan portal_cpan manager_cpan
for i in Common Portal Handler Manager; do \

View File

@ -119,8 +119,13 @@
"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\"",
"(?#Configuration)^/(manager\\.html|$)" : "$uid eq \"dwho\"",
"(?#Notifications)/notifications" : "$uid eq \"dwho\" or $uid eq \"rtyler\"",
"(?#Sessions)/sessions" : "$uid eq \"dwho\" or $uid eq \"rtyler\"",
"default" : "$uid eq \"dwho\" or $uid eq \"rtyler\""

6
debian/tests/runner vendored
View File

@ -10,8 +10,12 @@ TESTDIR=${BASE}/${TYPE}.d
LLSOURCEDIR=`pwd`
LIST=$2
test "$LIST" == "" 2>/dev/null && LIST=lemonldap-ng-*
EXITCODE=0
for LLLIB in lemonldap-ng-*; do
for LLLIB in $LIST; do
mkdir -p $LLSOURCEDIR/$LLLIB/debian/tests/pkg-perl
for llfile in debian/tests/pkg-perl/${LLLIB}*; do
if [ -r $llfile ]; then

View File

@ -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

View File

@ -147,7 +147,7 @@
"default" : "accept"
},
"manager.example.com": {
"(?#Configuration)^/(manager\\.html|conf/)": "$uid eq \"dwho\"",
"(?#Configuration)^/(manager\\.html|$)": "$uid eq \"dwho\"",
"(?#Notifications)^/notifications": "$uid eq \"dwho\" or $uid eq \"rtyler\"",
"(?#Sessions)^/sessions": "$uid eq \"dwho\" or $uid eq \"rtyler\"",
"default": "$uid eq \"dwho\" or $uid eq \"rtyler\""

View File

@ -0,0 +1 @@
--blib

View File

@ -51,6 +51,7 @@
"Crypt::OpenSSL::RSA" : "0",
"Crypt::OpenSSL::X509" : "0",
"Crypt::Rijndael" : "0",
"Crypt::URandom" : "0",
"Digest::SHA" : "0",
"HTML::Template" : "0",
"JSON" : "0",

View File

@ -36,6 +36,7 @@ requires:
Crypt::OpenSSL::RSA: '0'
Crypt::OpenSSL::X509: '0'
Crypt::Rijndael: '0'
Crypt::URandom: '0'
Digest::SHA: '0'
HTML::Template: '0'
JSON: '0'

View File

@ -360,7 +360,11 @@ languages = fr, en, vi, ar
; Manager modules enabled
; Set here the list of modules you want to see in manager interface
; The first will be used as default module displayed
enabledModules = conf, sessions, notifications, 2ndFA
enabledModules = conf, sessions, notifications, 2ndFA, viewer
; Viewer options - Default values
;viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes
;viewerAllowBrowser = 0
;[node-handler]
;

View File

@ -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
};
}

View File

@ -0,0 +1,3 @@
-I ../lemonldap-ng-common/blib/lib
-I .
--blib

View File

@ -0,0 +1,4 @@
-I .
-I ../lemonldap-ng-common/blib/lib
-I ../lemonldap-ng-handler/blib/lib
--blib

View File

@ -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

View File

@ -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'
},

View File

@ -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',

View File

@ -0,0 +1,78 @@
# 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;

View File

@ -0,0 +1,476 @@
###
LemonLDAP::NG Viewer client
This is the main app file. Other are:
- struct.json and js/confTree.js that contains the full tree
- translate.json that contains the keywords translation
This file contains:
- the AngularJS controller
###
llapp = angular.module 'llngManager', ['ui.tree', 'ui.bootstrap', 'llApp', 'ngCookies']
###
Main AngularJS controller
###
llapp.controller 'TreeCtrl', [
'$scope', '$http', '$location', '$q', '$uibModal', '$translator', '$cookies', '$htmlParams',
($scope, $http, $location, $q, $uibModal, $translator, $cookies, $htmlParams) ->
$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 = ''
# Import translations functions
$scope.translateTitle = (node) ->
return $translator.translateField node, 'title'
$scope.translateP = $translator.translateP
$scope.translate = $translator.translate
# HELP DISPLAY
$scope.helpUrl = 'start.html#configuration'
$scope.setShowHelp = (val) ->
val = !$scope.showH unless val?
$scope.showH = val
d = new Date(Date.now())
d.setFullYear(d.getFullYear() + 1)
$cookies.put 'showhelp', (if val then 'true' else 'false'), {"expires": d}
$scope.showH = if $cookies.get('showhelp') == 'false' then false else true
$scope.setShowHelp(true) unless $scope.showH?
# INTERCEPT AJAX ERRORS
readError = (response) ->
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'
# Modal launcher
$scope.showModal = (tpl, init) ->
modalInstance = $uibModal.open
templateUrl: tpl
controller: 'ModalInstanceCtrl'
size: 'lg'
resolve:
elem: ->
return (s) ->
return $scope[s]
set: ->
return (f, s) ->
$scope[f] = s
init: ->
return init
d = $q.defer()
modalInstance.result.then (msgok) ->
$scope.message =
title: ''
message: ''
items: []
d.resolve msgok
,(msgnok) ->
$scope.message =
title: ''
message: ''
items: []
d.reject msgnok
return d.promise
# FORM DISPLAY FUNCTIONS
# Function called when a menu item is selected. It launch function stored in
# "action" or "title"
$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
# Display main form
$scope.home = ->
$scope.form = 'home'
$scope.showM = false
# SAVE FUNCTIONS
# # Private method called by $scope.save()
# _checkSaveResponse = (data) ->
# $scope.message =
# title: ''
# message: ''
# items: []
# $scope.confirmNeeded = true if data.needConfirm
# $scope.message.message = data.message if data.message
# if data.details
# for m of data.details when m != '__changes__'
# if m == '__needConfirmation__'
# $scope.message.items.unshift
# message: m
# items: data.details[m]
# else
# $scope.message.items.push
# message: m
# items: data.details[m]
# $scope.waiting = false
# if data.result == 1
# # Force reloading page
# $location.path '/confs/'
# $scope.message.title = 'successfullySaved'
# else
# $scope.message.title = 'saveReport'
# $scope.showModal 'message.html'
# Download raw conf
$scope.downloadConf = () ->
window.open $scope.viewPrefix + $scope.currentCfg.cfgNum + '?full=1'
# NODES MANAGEMENT
id = 1
$scope._findContainer = ->
return $scope._findScopeContainer().$modelValue
$scope._findScopeContainer = ->
cs = $scope.currentScope
while not cs.$modelValue.type.match(/Container$/)
cs = cs.$parentNodeScope
return cs
$scope._findScopeByKey = (k) ->
cs = $scope.currentScope
while not (cs.$modelValue.title == k)
cs = cs.$parentNodeScope
return cs
_getAll = (node) ->
d = $q.defer()
d2 = $q.defer()
if node._nodes
_stoggle node
d.resolve()
else if node.cnodes
_download(node).then ->
d.resolve()
else if node.nodes or node.data
d.resolve()
else
$scope.getKey(node).then ->
d.resolve()
d.promise.then ->
t = []
if node.nodes
for n in node.nodes
t.push _getAll(n)
$q.all(t).then ->
d2.resolve()
return d2.promise
$scope.down = ->
id = $scope.currentNode.id
p = $scope.currentScope.$parentNodeScope.$modelValue
ind = p.nodes.length
for n, i in p.nodes
if n.id == id then ind = i
if ind < p.nodes.length - 1
tmp = p.nodes[ind]
p.nodes[ind] = p.nodes[ind + 1]
p.nodes[ind + 1] = tmp
ind
$scope.up = ->
id = $scope.currentNode.id
p = $scope.currentScope.$parentNodeScope.$modelValue
ind = -1
for n, i in p.nodes
if n.id == id then ind = i
if ind > 0
tmp = p.nodes[ind]
p.nodes[ind] = p.nodes[ind - 1]
p.nodes[ind - 1] = tmp
ind
# test if value is in select
$scope.inSelect = (value) ->
for n in $scope.currentNode.select
return true if n.k == value
return false
# This is for rule form: title = comment if defined, else title = re
$scope.changeRuleTitle = (node) ->
node.title = if node.comment.length > 0 then node.comment else node.re
# Node opening
# authParams mechanism: show used auth modules only (launched by stoggle)
$scope.filters = {}
$scope.execFilters = (scope) ->
scope = if scope then scope else $scope
for filter,func of $scope.filters
if $scope.filters.hasOwnProperty(filter)
return window.filterFunctions[filter](scope, $q, func)
false
# To avoid binding all the tree, nodes are pushed in DOM only when opened
$scope.stoggle = (scope) ->
node = scope.$modelValue
_stoggle node
scope.toggle()
_stoggle = (node) ->
for n in ['nodes', 'nodes_cond']
if node["_#{n}"]
node[n] = []
for a in node["_#{n}"]
node[n].push a
delete node["_#{n}"]
# Call execFilter for authParams
if node._nodes_filter
if node.nodes
for n in node.nodes
n.onChange = $scope.execFilters
$scope.filters[node._nodes_filter] = node
$scope.execFilters()
# Simple toggle management
$scope.toggle = (scope) ->
scope.toggle()
# cnodes management: hash keys/values are loaded when parent node is opened
$scope.download = (scope) ->
node = scope.$modelValue
return _download(node)
_download = (node) ->
d = $q.defer()
d.notify 'Trying to get datas'
$scope.waiting = true
$http.get("#{window.viewPrefix}#{$scope.currentCfg.cfgNum}/#{node.cnodes}").then (response) ->
data = response.data
# Manage datas errors
if not 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
# Store datas
delete node.cnodes
if not node.type
node.type = 'keyTextContainer'
node.nodes = []
# TODO: try/catch
for a in data
if a.template
a._nodes = templates a.template, a.title
node.nodes.push a
d.resolve 'OK'
$scope.waiting = false
, (response) ->
readError response
d.reject ''
return d.promise
$scope.openCnode = (scope) ->
$scope.download(scope).then ->
scope.toggle()
setHelp = (scope) ->
while !scope.$modelValue.help and scope.$parentNodeScope
scope = scope.$parentNodeScope
$scope.helpUrl = scope.$modelValue.help || 'start.html#configuration'
$scope.displayForm = (scope) ->
node = scope.$modelValue
if node.cnodes
$scope.download scope
if node._nodes
$scope.stoggle scope
$scope.currentNode = node
$scope.currentScope = scope
f = if node.type then node.type else 'text'
if node.nodes || node._nodes || node.cnodes
$scope.form = if f != 'text' then f else 'mini'
else
$scope.form = f
# Get datas
$scope.getKey node
if node.type and node.type == 'simpleInputContainer'
for n in node.nodes
$scope.getKey(n)
$scope.showT = false
setHelp scope
# method `getKey()`:
# - return a promise with the data:
# - from node when set
# - after downloading else
#
$scope.getKey = (node) ->
d = $q.defer()
if !node.data
$scope.waiting = true
if node.get and typeof(node.get) == 'object'
node.data = []
tmp = []
for n, i in node.get
node.data[i] =
title: n
id: n
tmp.push $scope.getKey(node.data[i])
$q.all(tmp).then ->
d.resolve(node.data)
,(response) ->
d.reject response.statusLine
$scope.waiting = false
else
$http.get("#{window.viewPrefix}#{$scope.currentCfg.cfgNum}/#{if node.get then node.get else node.title}").then (response) ->
# Set default value if response is null or if asked by server
data = response.data
if (data.value == null or (data.error and data.error.match /setDefault$/ ) ) and node['default'] != null
node.data = node['default']
else
node.data = data.value
# Cast int as int (remember that booleans are int for Perl)
if node.type and node.type.match /^int$/
node.data = parseInt(node.data, 10)
# Split SAML types
else if node.type and node.type.match(/^(saml(Service|Assertion)|blackWhiteList)$/) and not (typeof node.data == 'object')
node.data = node.data.split ';'
$scope.waiting = false
d.resolve node.data
, (response) ->
readError response
d.reject response.status
else
d.resolve node.data
return d.promise
# function `pathEvent(event, next; current)`:
# Called when $location.path() change, launch getCfg() with the new
# configuration number
pathEvent = (event, next, current) ->
n = next.match(new RegExp('#!?/view/(latest|[0-9]+)'))
if n == null
$location.path '/view/latest'
else
console.log "Trying to get cfg number #{n[1]}"
$scope.getCfg n[1]
$scope.$on '$locationChangeSuccess', pathEvent
# function `getCfg(n)`:
# Download configuration metadatas
$scope.getCfg = (n) ->
if $scope.currentCfg.cfgNum != n
$http.get("#{window.viewPrefix}#{n}").then (response) ->
$scope.currentCfg = response.data
d = new Date $scope.currentCfg.cfgDate * 1000
$scope.currentCfg.date = d.toLocaleString()
console.log "Metadatas of cfg #{n} loaded"
$location.path "/view/#{n}"
$scope.init()
, (response) ->
readError(response).then ->
$scope.currentCfg.cfgNum = 0
$scope.init()
else
$scope.waiting = false
# method `getLanguage(lang)`
# Launch init() after setting current language
$scope.getLanguage = (lang) ->
$scope.lang = lang
# Force reload home
$scope.form = 'white'
$scope.init()
$scope.showM = false
# Initialization
# Load JSON files:
# - struct.json: the main tree
# - languages/<lang>.json: the chosen language datas
$scope.init = ->
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 (response) ->
tmp = response.data
console.log("Structure loaded")
]
.then ->
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'
$scope.waiting = false
, readError
# Colorized link
$scope.activeModule = "viewer"
$scope.myStyle = {color: '#ffb84d'}
c = $location.path().match(new RegExp('^/view/(latest|[0-9]+)'))
unless c
console.log "Redirecting to /view/latest"
$location.path '/view/latest'
]

View File

@ -1,10 +1,14 @@
<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>

View File

@ -0,0 +1,539 @@
// Generated by CoffeeScript 1.12.7
/*
LemonLDAP::NG Viewer client
This is the main app file. Other are:
- struct.json and js/confTree.js that contains the full tree
- translate.json that contains the keywords translation
This file contains:
- the AngularJS controller
*/
(function() {
var llapp;
llapp = angular.module('llngManager', ['ui.tree', 'ui.bootstrap', 'llApp', 'ngCookies']);
/*
Main AngularJS controller
*/
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 * 1000);
$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);

File diff suppressed because one or more lines are too long

View File

@ -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":"المضيفين الإفتراضيين",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -21,7 +21,7 @@
<!-- Last buttons, available languages -->
</div>
<ul class="hidden-xs nav navbar-nav" role="grid">
<li ng-repeat="l in links" id="l in links"><a href="{{l.target}}" role="row" ng-mousedown="clickStyle={color: '#ffb84d'}"><strong><i ng-if="activeModule == l.title" ng-style="myStyle" class="glyphicon glyphicon-{{l.icon}}"></i><i ng-if="activeModule != l.title" class="glyphicon glyphicon-{{l.icon}}" ng-style="clickStyle"></i> <span ng-if="activeModule == l.title" ng-style="myStyle" ng-bind="translate(l.title)"></span><span ng-if="activeModule != l.title" ng-bind="translate(l.title)" ng-style="clickStyle"></span></strong></a></li>
<li ng-repeat="l in links" id="l in links"><a href="{{l.target}}" role="row" ng-mouseup="clickStyle={color: '#ffb84d'}"><strong><i ng-if="activeModule == l.title" ng-style="myStyle" class="glyphicon glyphicon-{{l.icon}}"></i><i ng-if="activeModule != l.title" class="glyphicon glyphicon-{{l.icon}}" ng-style="clickStyle"></i> <span ng-if="activeModule == l.title" ng-style="myStyle" ng-bind="translate(l.title)"></span><span ng-if="activeModule != l.title" ng-bind="translate(l.title)" ng-style="clickStyle"></span></strong></a></li>
</ul>
<ul class="hidden-xs nav navbar-nav navbar-right">
<li uib-dropdown>

View File

@ -0,0 +1,198 @@
<TMPL_INCLUDE NAME="header.tpl">
<title>LemonLDAP::NG Manager</title>
<link rel="prefetch" href="<TMPL_VAR NAME="STATIC_PREFIX">forms/home.html" />
<link rel="prefetch" href="<TMPL_VAR NAME="STATIC_PREFIX">struct.json" />
</head>
<body ng-app="llngManager" ng-controller="TreeCtrl" ng-csp>
<TMPL_INCLUDE NAME="menubar.tpl">
<div id="content" class="row container-fluid">
<TMPL_INCLUDE NAME="tree.tpl">
<!-- Right(main) div -->
<div id="right" class="col-lg-8 col-md-8 col-sm-7 col-xs-12 scrollable" ng-class="{'hidden-xs':showT&&!showM}">
<!-- Form container -->
<div id="top">
<!-- Menu buttons -->
<div class="lmmenu navbar navbar-default" ng-class="{'hidden-xs':!showM}">
<div class="navbar-collapse" ng-class="{'collapse':!showM}" id="formmenu">
<ul class="nav navbar-nav">
<li><a class="link" ng-click="home()"><i class="glyphicon glyphicon-home"></i></a></li>
<TMPL_IF NAME="ALLOWBROWSER">
<li uib-dropdown>
<a id="navmenu" name="menu" uib-dropdown-toggle data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="glyphicon glyphicon-cog"></i> {{translate('browse')}} <span class="caret"></span></a>
<ul uib-dropdown-menu aria-labelled-by="navmenu">
<li ng-class="{'disabled':!currentCfg.prev}"><a class="link" ng-click="currentCfg.prev && getCfg(currentCfg.prev)" title="Configuration {{currentCfg.prev}}"><i class="glyphicon glyphicon-arrow-left"></i> {{translate('previous')}}</a></li>
<li ng-class="{'disabled':!currentCfg.next}"><a class="link" ng-click="currentCfg.next && getCfg(currentCfg.next)" title="Configuration {{currentCfg.next}}"><i class="glyphicon glyphicon-arrow-right"></i> {{translate('next')}}</a></a></li>
<li><a class="link" ng-click="getCfg('latest')" title="Latest configuration"><i class="glyphicon glyphicon-refresh"></i> {{translate('latest')}}</a></li>
</ul>
</li>
</TMPL_IF>
<li><a class="link hidden-xs" ng-click="setShowHelp()"><i class="glyphicon" ng-class="{'glyphicon-eye-close': showH,'glyphicon-eye-open': !showH}" ></i> {{ translate((showH ? 'hideHelp' : 'showHelp')) }}</a></li>
<li uib-dropdown class="visible-xs">
<a id="langmenu" name="menu" uib-dropdown-toggle data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{translate('menu')}} <span class="caret"></span></a>
<ul uib-dropdown-menu aria-labelled-by="langmenu" role="grid">
<li ng-repeat="link in links"><a href="{{link.target}}" role="row"><i ng-if="link.icon" class="glyphicon glyphicon-{{link.icon}}"></i> {{translate(link.title)}}</a></li>
<li ng-repeat="menulink in menulinks"><a href="{{menulink.target}}" role="row"><i ng-if="menulink.icon" class="glyphicon glyphicon-{{menulink.icon}}"></i> {{translate(menulink.title)}}</a></li>
<li ng-include="'languages.html'"></li>
</ul>
</li>
<li>
<i class="glyphicon glyphicon-lock"></i>
<u>{{translate('readOnlyMode')}}</u>
</li>
</ul>
</div>
</div>
<form class="form-group slide-animate-container" ng-include="formPrefix+form+'.html'" scope="$scope" />
</div>
<!-- Help container -->
<div id="bottom" ng-if="showH" class="hidden-xs">
<div class="panel panel-default">
<div class="panel-body">
<iframe id="helpframe" width="100%" height="100%" ng-src="{{translate('/doc/')+'pages/documentation/current/'+helpUrl}}" frameborder="0"></iframe>
</div>
</div>
</div>
</div>
</div>
<!-- HTML recursive templates (used in `ng-repeat... ng-include="'template.html'") -->
<!-- Tree nested node template -->
<script type="text/ng-template" id="nodes_renderer.html">
<div ui-tree-handle class="tree-node panel-info" ng-class="{'bg-info':this.$modelValue===currentNode,'tree-node-default':this.$modelValue!==currentNode}">
<!-- Glyph icons -->
<span ng-switch="node.nodes||node.nodes_cond?1:((node._nodes&&node._nodes.length>0)||(node._nodes_cond&&node._nodes_cond.length>0)?3:(node.cnodes&&node.cnodes.length>0?2:0))">
<!-- Undownloaded nodes (hash data)-->
<a class="btn btn-sm" id="a-{{node.id}}" ng-switch-when="2" ng-click="openCnode(this)">
<span class="glyphicon glyphicon-chevron-right"></span>
</a>
<!-- Javascript nodes not yet bind to DOM -->
<a class="btn btn-sm" id="a-{{node.id}}" ng-switch-when="3" ng-click="stoggle(this)">
<span class="glyphicon" ng-class="{'glyphicon-chevron-right': collapsed, 'glyphicon-chevron-down': !collapsed}"></span>
</a>
<!-- Nodes already loaded and binded -->
<a class="btn btn-sm" id="a-{{node.id}}" ng-switch-when="1" ng-click="toggle(this)">
<span class="glyphicon" ng-class="{'glyphicon-chevron-right': collapsed, 'glyphicon-chevron-down': !collapsed}"></span>
</a>
<!-- Leaf -->
<a class="btn btn-sm" ng-switch-default ng-click="displayForm(this)">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</span>
<!-- Node text with/without translation -->
<span id="t-{{node.id}}" ng-if="keyWritable(this)" ng-click="displayForm(this)">{{node.title}}</span>
<span id="t-{{node.id}}" ng-if="!keyWritable(this)" ng-click="displayForm(this)" trspan="{{node.title}}" />
</div>
<!-- Subnodes -->
<ol ui-tree-nodes="btn btn-sm" ng-model="node.nodes" ng-class="{hidden: collapsed}">
<li ng-repeat="node in node.nodes track by node.id" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li>
</ol>
<!-- Filtered subnodes (authParams mechanism) -->
<ol ui-tree-nodes="btn btn-sm" ng-model="node.nodes_cond" ng-class="{hidden: collapsed}">
<li ng-repeat="(name,node) in node.nodes_cond track by node.id" ng-if="node.show" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li>
</ol>
</script>
<!-- Prompt -->
<script type="text/ng-template" id="prompt.html">
<div role="alertdialog" aria-labelledby="ptitle" aria-describedby="ptitle">
<div class="modal-header">
<h3 id="ptitle" class="modal-title" trspan="{{elem('message').title}}" />
</div>
<div class="modal-body">
<div class="input-group maxw">
<label class="input-group-addon" id="promptlabel" for="promptinput" trspan="{{elem('message').field}}"/>
<input id="promptinput" class="form-control" ng-model="result" aria-describedby="promptlabel"/>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="promptok" ng-click="ok()" trspan="ok" role="button"></button>
<button class="btn btn-warning" ng-click="cancel()" trspan="cancel" role="button"></button>
</div>
</div>
</script>
<!-- Message display -->
<script type="text/ng-template" id="message.html">
<div role="alertdialog" aria-labelledby="mtitle" aria-describedby="mbody">
<div class="modal-header">
<h3 id="mtitle" class="modal-title" trspan="{{elem('message').title}}" />
</div>
<div id="mbody" class="modal-body">
<div class="modal-p">{{translateP(elem('message').message)}}</div>
<ul class="main-modal-ul" ng-model="elem('message').items">
<li ng-repeat="item in elem('message').items" ng-include="'messageitem.html'"/>
</ul>
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="messageok" ng-click="ok()" trspan="ok" role="button"></button>
<button class="btn btn-warning" ng-click="cancel()" ng-if="elem('message').displayCancel" trspan="cancel" role="button"></button>
</div>
</div>
</script>
<script type="text/ng-template" id="messageitem.html">
<div class="modal-p">{{translateP(item.message)}}</div>
<ul class="modal-ul" ng-model="item.items">
<li ng-repeat="item in item.items" ng-include="'messageitem.html'"/>
</ul>
</script>
<!-- Password question -->
<script type="text/ng-template" id="password.html">
<div role="alertdialog" aria-labelledby="pwtitle" aria-describedby="pwtitle">
<div class="modal-header">
<h3 id="pwtitle" class="modal-title" trspan="enterPassword" />
</div>
<div class="modal-body">
<div class="input-group maxw">
<label class="input-group-addon" id="mlabel" for="mdPwd" trspan="password"/>
<input id="mdPwd" class="form-control" ng-model="result" aria-describedby="mlabel"/>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="passwordok" ng-click="ok()" trspan="ok" role="button"></button>
<button class="btn btn-warning" ng-click="cancel()" trspan="cancel" role="button"></button>
</div>
</div>
</script>
<!-- Save confirm -->
<script type="text/ng-template" id="save.html">
<div role="alertdialog" aria-labelledby="stitle" aria-describedby="sbody">
<div class="modal-header">
<h3 id="stitle" class="modal-title" trspan="savingConfirmation" />
</div>
<div id="sbody" class="modal-body">
<div class="input-group maxw">
<label id="slabel" class="input-group-addon" for="longtextinput" trspan="cfgLog"/>
<textarea id="longtextinput" rows="5" class="form-control" ng-model="result" aria-describedby="slabel" />
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="saveok" ng-click="ok()" trspan="ok" role="button"></button>
<button class="btn btn-warning" ng-click="cancel()" trspan="cancel" role="button"></button>
</div>
</div>
</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/filterFunctions.min.js"></script>
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/viewer.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/filterFunctions.js"></script>
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/viewer.js"></script>
<!-- //endif -->
<TMPL_INCLUDE NAME="footer.tpl">

View File

@ -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',

View File

@ -0,0 +1,4 @@
-I .
-I ../lemonldap-ng-common/blib/lib
-I ../lemonldap-ng-handler/blib/lib
--blib

View File

@ -352,8 +352,7 @@ sub display {
# * Bad URL error
elsif ($req->{error} == PE_LOGOUT_OK
or $req->{error} == PE_WAIT
or $req->{error} == PE_BADURL
or $req->{error} == PE_BADCREDENTIALS )
or $req->{error} == PE_BADURL )
{
%templateParams = (
%templateParams,

View File

@ -210,6 +210,12 @@ sub do {
if ( $err == PE_SENDRESPONSE ) {
return $req->response;
}
# Remove userData if authentication fails
if ( $err == PE_BADCREDENTIALS ) {
$req->userData( {} );
}
if ( !$self->conf->{noAjaxHook} and $req->wantJSON ) {
$self->logger->debug('Processing to JSON response');
if ( ( $err > 0 and !$req->id ) or $err eq PE_SESSIONNOTGRANTED ) {

View File

@ -45,7 +45,11 @@ sub run {
}
# Avoid display notification if AuthResult is not null
return PE_BADCREDENTIALS if $req->authResult > PE_OK;
if ( $req->authResult > PE_OK ) {
$self->logger->debug(
"Bad authentication, do not check grant session rules");
return PE_BADCREDENTIALS;
}
foreach ( sort sortByComment keys %{ $self->rules } ) {
$self->logger->debug( "Grant session condition -> "

View File

@ -29,8 +29,53 @@ ok( $res->[2]->[0] =~ m%<span id="languages"></span>%, ' Language icons found' )
or print STDERR Dumper( $res->[2]->[0] );
count(2);
# Try to authenticate
# -------------------
# Try to authenticate with unknown user
# -------------------------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=jdoe&password=jdoe'),
accept => 'text/html',
length => 23
),
'Auth query'
);
count(1);
ok(
$res->[2]->[0] =~ /<span trmsg="5"><\/span><\/div>/,
'jdoe rejected with PE_BADCREDENTIALS'
) or print STDERR Dumper( $res->[2]->[0] );
count(1);
ok( $res->[2]->[0] =~ m%<span trspan="connect">Connect</span>%,
'Found connect button' )
or print STDERR Dumper( $res->[2]->[0] );
count(1);
# Try to authenticate with bad password
# -------------------------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=jdoe'),
accept => 'text/html',
length => 23
),
'Auth query'
);
count(1);
ok(
$res->[2]->[0] =~ /<span trmsg="5"><\/span><\/div>/,
'dwho rejected with PE_BADCREDENTIALS'
) or print STDERR Dumper( $res->[2]->[0] );
count(1);
ok( $res->[2]->[0] =~ m%<span trspan="connect">Connect</span>%,
'Found connect button' )
or print STDERR Dumper( $res->[2]->[0] );
count(1);
# Try to authenticate with good password
# --------------------------------------
ok(
$res = $client->_post(
'/',

View File

@ -54,8 +54,8 @@ ok( $res->[2]->[0] =~ /<span trmsg="5"><\/span><\/div>/,
'dwho rejected with PE_BADCREDENTIALS' )
or print STDERR Dumper( $res->[2]->[0] );
count(1);
ok( $res->[2]->[0] =~ m%<span trspan="goToPortal">Go to portal</span>%,
'Found goToPortal button' )
ok( $res->[2]->[0] =~ m%<span trspan="connect">Connect</span>%,
'Found connect button' )
or print STDERR Dumper( $res->[2]->[0] );
count(1);
@ -125,8 +125,8 @@ ok(
'rtyler rejected with PE_BADCREDENTIALS'
) or print STDERR Dumper( $res->[2]->[0] );
count(1);
ok( $res->[2]->[0] =~ m%<span trspan="goToPortal">Go to portal</span>%,
'Found goToPortal button' )
ok( $res->[2]->[0] =~ m%<span trspan="connect">Connect</span>%,
'Found connect button' )
or print STDERR Dumper( $res->[2]->[0] );
count(1);