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

Merge branch 'v2.0'

parents ab292a03 68ae4129
......@@ -428,6 +428,7 @@ prepare_test_server:
ETCDEFAULTDIR=`pwd`/e2e-tests/conf/def
#@cp -f e2e-tests/index.* e2e-tests/conf/
@cp -f $(SRCMANAGERDIR)/site/htdocs/manager* e2e-tests/conf/manager
@cp -f $(SRCMANAGERDIR)/site/htdocs/api* e2e-tests/conf/manager
@cp -f $(SRCPORTALDIR)/site/htdocs/index* e2e-tests/conf/portal
@cp e2e-tests/persistent/5efe8af397fc3577e05b483aca964f1b e2e-tests/conf/persistents
@cp e2e-tests/saml-sp.xml e2e-tests/conf/site/saml-sp.xml
......
......@@ -99,3 +99,76 @@
# Uncomment this if site if you use SSL only
#Header set Strict-Transport-Security "max-age=15768000"
</VirtualHost>
# API virtual host (manager.__DNSDOMAIN__)
<VirtualHost __VHOSTLISTEN__>
ServerName api.__DNSDOMAIN__
LogLevel notice
# See above to set LLNG user id in Apache logs
#CustomLog __APACHELOGDIR__/manager.log llng
#ErrorLog __APACHELOGDIR__/lm_err.log
# Uncomment this if you are running behind a reverse proxy and want
# LemonLDAP::NG to see the real IP address of the end user
# Adjust the settings to match the IP address of your reverse proxy
# and the header containing the original IP address
#
#RemoteIPHeader X-Forwarded-For
#RemoteIPInternalProxy 127.0.0.1
# FASTCGI CONFIGURATION
# ---------------------
# 1) URI management
RewriteEngine on
# For performances, you can delete the previous RewriteRule line after
# puttings html files: simply put the HTML results of differents modules
# (configuration, sessions, notifications) as manager.html, sessions.html,
# notifications.html and uncomment the 2 following lines:
# DirectoryIndex manager.html
# RewriteCond "%{REQUEST_URI}" "!\.html(?:/.*)?$"
# REST URLs
RewriteCond "%{REQUEST_URI}" "!^/(?:static|doc|lib|javascript|favicon).*"
RewriteRule "^/(.+)$" "/api.fcgi/$1" [PT]
# 2) FastCGI engine
# You can choose any FastCGI system. Here is an example using mod_fcgid
# mod_fcgid configuration
FcgidMaxRequestLen 2000000
<Files *.fcgi>
SetHandler fcgid-script
Options +ExecCGI
header unset Lm-Remote-User
</Files>
# If you want to use mod_fastcgi, replace lines below by:
#FastCgiServer __MANAGERSITEDIR__/manager.fcgi
# GLOBAL CONFIGURATION
# --------------------
DocumentRoot __MANAGERSITEDIR__
<Location />
Require all denied
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css
SetOutputFilter DEFLATE
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
</IfModule>
<IfModule mod_headers.c>
Header append Vary User-Agent env=!dont-vary
</IfModule>
</Location>
# Uncomment this if site if you use SSL only
#Header set Strict-Transport-Security "max-age=15768000"
</VirtualHost>
......@@ -118,3 +118,83 @@
# Uncomment this if site if you use SSL only
#Header set Strict-Transport-Security "max-age=15768000"
</VirtualHost>
# API virtual host (manager.__DNSDOMAIN__)
<VirtualHost __VHOSTLISTEN__>
ServerName api.__DNSDOMAIN__
LogLevel notice
# See above to set LLNG user id in Apache logs
#CustomLog __APACHELOGDIR__/manager.log llng
#ErrorLog __APACHELOGDIR__/lm_err.log
# Uncomment this if you are running behind a reverse proxy and want
# LemonLDAP::NG to see the real IP address of the end user
# Adjust the settings to match the IP address of your reverse proxy
# and the header containing the original IP address
#
#RemoteIPHeader X-Forwarded-For
#RemoteIPInternalProxy 127.0.0.1
# FASTCGI CONFIGURATION
# ---------------------
# 1) URI management
RewriteEngine on
# For performances, you can delete the previous RewriteRule line after
# puttings html files: simply put the HTML results of differents modules
# (configuration, sessions, notifications) as manager.html, sessions.html,
# notifications.html and uncomment the 2 following lines:
# DirectoryIndex manager.html
# RewriteCond "%{REQUEST_URI}" "!\.html(?:/.*)?$"
# REST URLs
RewriteCond "%{REQUEST_URI}" "!^/(?:static|doc|lib|javascript|favicon).*"
RewriteRule "^/(.+)$" "/api.fcgi/$1" [PT]
# 2) FastCGI engine
# You can choose any FastCGI system. Here is an example using mod_fcgid
# mod_fcgid configuration
FcgidMaxRequestLen 2000000
<Files *.fcgi>
SetHandler fcgid-script
Options +ExecCGI
header unset Lm-Remote-User
</Files>
# If you want to use mod_fastcgi, replace lines below by:
#FastCgiServer __MANAGERSITEDIR__/manager.fcgi
# GLOBAL CONFIGURATION
# --------------------
DocumentRoot __MANAGERSITEDIR__
<Location />
<IfVersion >= 2.3>
Require all denied
</IfVersion>
<IfVersion < 2.3>
Order Deny,Allow
Deny from all
</IfVersion>
Options +FollowSymLinks
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css
SetOutputFilter DEFLATE
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
</IfModule>
<IfModule mod_headers.c>
Header append Vary User-Agent env=!dont-vary
</IfModule>
</Location>
# Uncomment this if site if you use SSL only
#Header set Strict-Transport-Security "max-age=15768000"
</VirtualHost>
......@@ -102,3 +102,77 @@
# Uncomment this if site if you use SSL only
#Header set Strict-Transport-Security "max-age=15768000"
</VirtualHost>
# API virtual host (api.__DNSDOMAIN__)
<VirtualHost __VHOSTLISTEN__>
ServerName api.__DNSDOMAIN__
LogLevel notice
# See above to set LLNG user id in Apache logs
#CustomLog __APACHELOGDIR__/manager.log llng
#ErrorLog __APACHELOGDIR__/lm_err.log
# Uncomment this if you are running behind a reverse proxy and want
# LemonLDAP::NG to see the real IP address of the end user
# Adjust the settings to match the IP address of your reverse proxy
# and the header containing the original IP address
#
#RemoteIPHeader X-Forwarded-For
#RemoteIPInternalProxy 127.0.0.1
# FASTCGI CONFIGURATION
# ---------------------
# 1) URI management
RewriteEngine on
# For performances, you can delete the previous RewriteRule line after
# puttings html files: simply put the HTML results of differents modules
# (configuration, sessions, notifications) as manager.html, sessions.html,
# notifications.html and uncomment the 2 following lines:
# DirectoryIndex manager.html
# RewriteCond "%{REQUEST_URI}" "!\.html(?:/.*)?$"
# REST URLs
RewriteCond "%{REQUEST_URI}" "!^/(?:static|doc|lib|javascript|favicon).*"
RewriteRule "^/(.+)$" "/api.fcgi/$1" [PT]
# 2) FastCGI engine
# You can choose any FastCGI system. Here is an example using mod_fcgid
# mod_fcgid configuration
FcgidMaxRequestLen 2000000
<Files *.fcgi>
SetHandler fcgid-script
Options +ExecCGI
header unset Lm-Remote-User
</Files>
# If you want to use mod_fastcgi, replace lines below by:
#FastCgiServer __MANAGERSITEDIR__/manager.fcgi
# GLOBAL CONFIGURATION
# --------------------
DocumentRoot __MANAGERSITEDIR__
<Location />
Order Deny,Allow
Deny from all
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css
SetOutputFilter DEFLATE
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
</IfModule>
<IfModule mod_headers.c>
Header append Vary User-Agent env=!dont-vary
</IfModule>
</Location>
# Uncomment this if site if you use SSL only
#Header set Strict-Transport-Security "max-age=15768000"
</VirtualHost>
......@@ -38,7 +38,7 @@ our $authParameters = {
casParams => [qw(casAuthnLevel)],
choiceParams => [qw(authChoiceParam authChoiceModules authChoiceAuthBasic)],
combinationParams => [qw(combination combModules combinationForms)],
customParams => [qw(customAuth customUserDB customPassword customRegister customAddParams)],
customParams => [qw(customAuth customUserDB customPassword customRegister customResetCertByMail customAddParams)],
dbiParams => [qw(dbiAuthnLevel dbiExportedVars dbiAuthChain dbiAuthUser dbiAuthPassword dbiUserChain dbiUserUser dbiUserPassword dbiAuthTable dbiUserTable dbiAuthLoginCol dbiAuthPasswordCol dbiPasswordMailCol userPivot dbiAuthPasswordHash dbiDynamicHashEnabled dbiDynamicHashValidSchemes dbiDynamicHashValidSaltedSchemes dbiDynamicHashNewPasswordScheme)],
demoParams => [qw(demoExportedVars)],
facebookParams => [qw(facebookAuthnLevel facebookExportedVars facebookAppId facebookAppSecret facebookUserField)],
......
......@@ -92,6 +92,35 @@ sub _put {
);
}
sub _patch {
my ( $self, $path, $query, $body, $type, $len ) = @_;
die "$body must be a IO::Handle"
unless ( ref($body) and $body->can('read') );
return $self->app->( {
'HTTP_ACCEPT' => 'application/json, text/plain, */*',
'SCRIPT_NAME' => '',
'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
'SERVER_NAME' => '127.0.0.1',
'QUERY_STRING' => $query,
'HTTP_CACHE_CONTROL' => 'max-age=0',
'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3',
'PATH_INFO' => $path,
'REQUEST_METHOD' => 'PATCH',
'REQUEST_URI' => $path . ( $query ? "?$query" : '' ),
'SERVER_PORT' => '8002',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'HTTP_USER_AGENT' =>
'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox',
'REMOTE_ADDR' => '127.0.0.1',
'HTTP_HOST' => '127.0.0.1:8002',
'psgix.input.buffered' => 1,
'psgi.input' => $body,
'CONTENT_LENGTH' => $len // scalar( ( stat $body )[7] ),
'CONTENT_TYPE' => $type,
}
);
}
sub _del {
my ( $self, $path, $query ) = @_;
return $self->app->( {
......
......@@ -129,18 +129,18 @@ PSGIs
=head1 SYNOPSIS
package My::PSGI;
use base Lemonldap::NG::Common::PSGI;
# See Lemonldap::NG::Common::PSGI
...
sub handler {
my ( $self, $req ) = @_;
# Do something and return a PSGI response
# NB: $req is a Lemonldap::NG::Common::PSGI::Request object
if ( $req->accept eq 'text/plain' ) { ... }
return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Body lines' ] ];
}
......
......@@ -12,7 +12,7 @@ extends 'Lemonldap::NG::Common::PSGI';
has 'routes' => (
is => 'rw',
isa => 'HashRef',
default => sub { { GET => {}, POST => {}, PUT => {}, DELETE => {} } }
default => sub { { GET => {}, POST => {}, PUT => {}, PATCH => {}, DELETE => {} } }
);
has 'defaultRoute' => ( is => 'rw', default => 'index.html' );
......@@ -20,7 +20,7 @@ has 'defaultRoute' => ( is => 'rw', default => 'index.html' );
sub addRoute {
my ( $self, $word, $dest, $methods, $transform ) = (@_);
$methods ||= [qw(GET POST PUT DELETE)];
$methods ||= [qw(GET POST PUT PATCH DELETE)];
foreach my $method (@$methods) {
$self->logger->debug("Add $method route:");
$self->genRoute( $self->routes->{$method}, $word, $dest, $transform );
......
......@@ -214,7 +214,7 @@ sub defaultValuesInit {
# Override with vhost options
if ( $conf->{vhostOptions} ) {
my $name = 'vhost' . ucfirst($opt);
foreach my $vhost ( keys %{ $conf->{vhostOptions} } ) {
foreach my $vhost ( sort keys %{ $conf->{vhostOptions} } ) {
$conf->{vhostOptions}->{$vhost} ||= {};
my $val = $conf->{vhostOptions}->{$vhost}->{$name};
......@@ -228,7 +228,7 @@ sub defaultValuesInit {
}
}
if ( $conf->{vhostOptions} ) {
foreach my $vhost ( keys %{ $conf->{vhostOptions} } ) {
foreach my $vhost ( sort keys %{ $conf->{vhostOptions} } ) {
$class->tsv->{type}->{$vhost} =
$conf->{vhostOptions}->{$vhost}->{vhostType};
$class->tsv->{authnLevel}->{$vhost} =
......@@ -326,7 +326,7 @@ sub locationRulesInit {
## @imethod protected void sessionStorageInit(hashRef args)
# Initialize the Apache::Session::* module choosed to share user's variables
# and the Cache::Cache module choosed to cache sessions
# and the Cache::Cache module chosen to cache sessions
# @param $args reference to the configuration hash
sub sessionStorageInit {
my ( $class, $conf ) = @_;
......
......@@ -7,6 +7,11 @@ eg/manager.psgi
KINEMATIC.md
lib/Lemonldap/NG/Manager.pm
lib/Lemonldap/NG/Manager/2ndFA.pm
lib/Lemonldap/NG/Manager/Api.pm
lib/Lemonldap/NG/Manager/Api/2F.pm
lib/Lemonldap/NG/Manager/Api/Common.pm
lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm
lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm
lib/Lemonldap/NG/Manager/Attributes.pm
lib/Lemonldap/NG/Manager/Build.pm
lib/Lemonldap/NG/Manager/Build/Attributes.pm
......@@ -40,6 +45,7 @@ site/coffee/notifications.coffee
site/coffee/sessions.coffee
site/coffee/viewDiff.coffee
site/coffee/viewer.coffee
site/htdocs/api.fcgi
site/htdocs/manager.fcgi
site/htdocs/manager.psgi
site/htdocs/static/bwr/angular-animate/angular-animate.js
......@@ -215,8 +221,10 @@ site/templates/viewDiff.tpl
site/templates/viewer.tpl
t/02-HTML-template.t
t/03-HTML-forms.t
t/04-2F-api.t
t/04-providers-api.t
t/05-rest-api.t
t/06-rest-api.t
t/06-rest-api-RSA.t
t/07-utf8.t
t/10-save-unchanged-conf.t
t/11-save-appCat-changed-conf.t
......
......@@ -52,7 +52,7 @@ sub init {
return 0;
}
$self->{enabledModules} ||= "conf, sessions, notifications, 2ndFA";
$self->{enabledModules} ||= "conf, sessions, notifications, 2ndFA, api";
my @links;
my @enabledModules =
map { push @links, $_; "Lemonldap::NG::Manager::" . ucfirst($_) }
......
# This module implements all the methods that responds to '/api/*' requests
package Lemonldap::NG::Manager::Api;
use 5.10.0;
use utf8;
use Mouse;
extends 'Lemonldap::NG::Common::Conf::RESTServer',
'Lemonldap::NG::Common::Session::REST';
use Lemonldap::NG::Manager::Api::2F;
use Lemonldap::NG::Manager::Api::Providers::OidcRp;
use Lemonldap::NG::Manager::Api::Providers::SamlSp;
our $VERSION = '2.0.7';
#############################
# I. INITIALIZATION METHODS #
#############################
use constant defaultRoute => 'api.html';
sub addRoutes {
my ( $self, $conf ) = @_;
# HTML template
$self->addRoute( 'api.html', undef, ['GET'] )
->addRoute(
api => {
v1 => {
providers => {
oidc => {
rp => {
findByConfKey => {
':uPattern' => 'findOidcRpByConfKey'
},
findByClientId => {
':uClientId' => 'findOidcRpByClientId'
},
':confKey' => 'getOidcRpByConfKey'
},
},
saml => {
sp => {
findByConfKey => {
':uPattern' => 'findSamlSpByConfKey'
},
findByEntityId => {
':uEntityId' => 'findSamlSpByEntityId'
},
':confKey' => 'getSamlSpByConfKey'
},
},
},
secondFactor => {
':uid' => {
id => {
':id' => 'getSecondFactorsById'
},
type => {
':type' => 'getSecondFactorsByType'
},
'*' => 'getSecondFactors'
},
},
},
},
['GET']
)
->addRoute(
api => {
v1 => {
providers => {
oidc => {
rp => 'addOidcRp'
},
saml => {
sp => 'addSamlSp'
},
},
},
},
['POST']
)
->addRoute(
api => {
v1 => {
providers => {
oidc => {
rp => { ':confKey' => 'replaceOidcRp' }
},
saml => {
sp => { ':confKey' => 'replaceSamlSp' }
},
},
},
},
['PUT']
)
->addRoute(
api => {
v1 => {
providers => {
oidc => {
rp => { ':confKey' => 'updateOidcRp' }
},
saml => {
sp => { ':confKey' => 'updateSamlSp' }
},
},
},
},
['PATCH']
)
->addRoute(
api => {
v1 => {
providers => {
oidc => {
rp => { ':confKey' => 'deleteOidcRp' }
},
saml => {
sp => { ':confKey' => 'deleteSamlSp' }
},
},
secondFactor => {
':uid' => {
id => {
':id' => 'deleteSecondFactorsById'
},
type => {
':type' => 'deleteSecondFactorsByType'
},
'*' => 'deleteSecondFactors'
},
},
},
},
['DELETE']
);
$self->setTypes($conf);
$self->{multiValuesSeparator} ||= '; ';
$self->{hiddenAttributes} //= "_password";
$self->{TOTPCheck} = $self->{U2FCheck} = $self->{UBKCheck} = '1';
}
1;
This diff is collapsed.
package Lemonldap::NG::Manager::Api::Common;
our $VERSION = '2.0.8';
package Lemonldap::NG::Manager::Api;
use Lemonldap::NG::Manager::Build::Attributes;
use Lemonldap::NG::Manager::Build::CTrees;
# use Scalar::Util 'weaken'; ?
sub _isSimpleKeyValueHash {
my ( $self, $hash ) = @_;
return 0 if ( ref($hash) ne "HASH" );
foreach ( keys %$hash ) {
return 0 if ( ref( $hash->{$_} ) ne '' || ref($_) ne '' );
}
return 1;
}
sub _setDefaultValues {
my ( $self, $attrs, $rootNode ) = @_;
my @allAttrs = $self->_listAttributes($rootNode);
my $defaultAttrs = Lemonldap::NG::Manager::Build::Attributes::attributes();
foreach $attr (@allAttrs) {
unless ( defined $attrs->{$attr} ) {
$attrs->{$attr} = $defaultAttrs->{$attr}->{default}
if ( defined $defaultAttrs->{$attr}
&& defined $defaultAttrs->{$attr}->{default} );
}
}
return $attrs;
}
sub _hasAllowedAttributes {
my ( $self, $attributes, $rootNode ) = @_;
my @allowedAttributes = $self->_listAttributes($rootNode);
foreach $attribute ( keys %{$attributes} ) {
if ( length( ref($attribute) ) ) {
return {
res => "ko",
msg => "Invalid input: Attribute $attribute is not a string."
};
}
unless ( grep { /^$attribute$/ } @allowedAttributes ) {
return {
res => "ko",
msg => "Invalid input: Attribute $attribute does not exist."
};
}
}
return { res => "ok" };
}
sub _listAttributes {
my ( $self, $rootNode ) = @_;
my $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees();
my $rootNodes = [ grep { ref($_) eq "HASH" } @{ $mainTree->{$rootNode} } ];
my @attributes = map { $self->_listNodeAttributes($_) } @$rootNodes;
return @attributes;
}
sub _listNodeAttributes {
my ( $self, $node ) = @_;
my @attributes =
map { ref($_) eq "HASH" ? $self->_listNodeAttributes($_) : $_ }
@{ $node->{nodes} };
return @attributes;
}
1;
......@@ -1086,6 +1086,9 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'customRegister' => {
'type' => 'text'
},
'customResetCertByMail' => {
'type' => 'text'
},
'customToTrace' => {
'type' => 'lmAttrOrMacro'
},
......
......@@ -3602,6 +3602,10 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
type => 'text',
documentation => 'Custom register module',
},
customResetCertByMail => {
type => 'text',
documentation => 'Custom certificateResetByMail module',
},
customAddParams => {
type => 'keyTextContainer',
documentation => 'Custom additional parameters',
......
......@@ -435,9 +435,9 @@ sub tree {
title => 'customParams',
help => 'authcustom.html',
nodes => [
'customAuth', 'customUserDB',
'customPassword', 'customRegister',
'customAddParams',
'customAuth', 'customUserDB',
'customPassword', 'customRegister',
'customResetCertByMail', 'customAddParams',
]
},
],
......