Commit f21bfe2c authored by Christophe Maudoux's avatar Christophe Maudoux

Merge branch 'master' into manager-u2f-module

parents 805cb525 5f068d4f
......@@ -871,6 +871,9 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
},
'type' => 'cmbModuleContainer'
},
'configStorage' => {
'type' => 'text'
},
'confirmFormMethod' => {
'default' => 'post',
'select' => [
......@@ -1457,6 +1460,12 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
},
'type' => 'keyTextContainer'
},
'localStorage' => {
'type' => 'text'
},
'localStorageOptions' => {
'type' => 'keyTextContainer'
},
'locationRules' => {
'default' => {
'default' => 'deny'
......@@ -3010,6 +3019,29 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
],
'type' => 'select'
},
'secureTokenAllowOnError' => {
'type' => 'text'
},
'secureTokenAttribute' => {
'default' => 'uid',
'type' => 'text'
},
'secureTokenExpiration' => {
'default' => 60,
'type' => 'text'
},
'secureTokenHeader' => {
'default' => 'Auth-Token',
'type' => 'text'
},
'secureTokenMemcachedServers' => {
'default' => '',
'type' => 'text'
},
'secureTokenUrls' => {
'default' => '.*',
'type' => 'text'
},
'sessionDataToRemember' => {
'keyMsgFail' => '__invalidSessionData__',
'keyTest' => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/,
......@@ -3124,6 +3156,9 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
'staticPrefix' => {
'type' => 'text'
},
'status' => {
'type' => 'bool'
},
'stayConnected' => {
'type' => 'bool'
},
......@@ -3364,6 +3399,21 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
},
'yubikeySecretKey' => {
'type' => 'text'
},
'zimbraAccountKey' => {
'type' => 'text'
},
'zimbraBy' => {
'type' => 'text'
},
'zimbraPreAuthKey' => {
'type' => 'text'
},
'zimbraSsoUrl' => {
'type' => 'text'
},
'zimbraUrl' => {
'type' => 'text'
}
};
}
......
......@@ -215,6 +215,21 @@ sub attributes {
return {
# Other
configStorage => {
type => 'text',
documentation => 'Configuration storage',
flags => 'hmp',
},
localStorage => {
type => 'text',
documentation => 'Local cache',
flags => 'hmp',
},
localStorageOptions => {
type => 'keyTextContainer',
documentation => 'Local cache',
flags => 'hmp',
},
cfgNum => {
type => 'int',
default => 0,
......@@ -240,6 +255,11 @@ sub attributes {
type => 'text',
documentation => 'Version of LLNG which build configuration',
},
status => {
type => 'bool',
documentation => 'Status daemon activation',
flags => 'h',
},
confirmFormMethod => {
type => "select",
select =>
......@@ -251,12 +271,14 @@ sub attributes {
type => 'text',
test => qr/^(?:\w+(?:::\w+)*(?:\s+\w+(?:::\w+)*)*)?$/,
msgFail => "__badCustomFuncName__",
documentation => 'List of custom functions'
documentation => 'List of custom functions',
flags => 'hmp',
},
https => {
default => 0,
type => 'bool',
documentation => 'Use HTTPS for redirection from portal',
flags => 'h',
},
infoFormMethod => {
type => "select",
......@@ -265,7 +287,11 @@ sub attributes {
default => 'get',
documentation => 'HTTP method for info page form',
},
port => { type => 'int', documentation => 'Force port in redirection' },
port => {
type => 'int',
documentation => 'Force port in redirection',
flags => 'h',
},
jsRedirect => {
type => 'boolOrExpr',
default => 0,
......@@ -281,6 +307,7 @@ sub attributes {
default => 0,
type => 'bool',
documentation => 'Maintenance mode for all virtual hosts',
flags => 'h',
},
nginxCustomHandlers => {
type => 'keyTextContainer',
......@@ -298,6 +325,7 @@ sub attributes {
type => 'url',
default => 'http://auth.example.com/',
documentation => 'Portal URL',
flags => 'hmp',
},
portalStatus => {
type => 'bool',
......@@ -334,6 +362,7 @@ sub attributes {
type => 'authParamsText',
default => '; ',
documentation => 'Separator for multiple values',
flags => 'hmp',
},
stayConnected => {
type => 'bool',
......@@ -347,6 +376,7 @@ sub attributes {
msgFail => '__authorizedValues__: none authenticate manager',
default => 'none',
documentation => 'Manager protection method',
flags => 'hm',
},
# Menu
......@@ -447,6 +477,7 @@ sub attributes {
default => 0,
type => 'bool',
documentation => 'Enable Cross Domain Authentication',
flags => 'hp',
},
checkXSS => {
default => 1,
......@@ -560,6 +591,7 @@ sub attributes {
type => 'bool',
default => 1,
documentation => 'Use 302 redirect code for error (500)',
flags => 'h',
},
useRedirectOnForbidden => {
default => 0,
......@@ -571,11 +603,13 @@ sub attributes {
type => 'bool',
help => 'safejail.html',
documentation => 'Activate Safe jail',
flags => 'hp',
},
whatToTrace => {
type => 'lmAttrOrMacro',
default => 'uid',
documentation => 'Session parameter used to fill REMOTE_USER',
flags => 'hp',
},
lwpOpts => {
type => 'keyTextContainer',
......@@ -641,14 +675,18 @@ sub attributes {
},
# Cookies
cookieExpiration =>
{ type => 'text', documentation => 'Cookie expiration', },
cookieExpiration => {
type => 'text',
documentation => 'Cookie expiration',
flags => 'hp',
},
cookieName => {
type => 'text',
test => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/,
msgFail => '__badCookieName__',
default => 'lemonldap',
documentation => 'Name of the main cookie',
flags => 'hp',
},
domain => {
type => 'text',
......@@ -656,11 +694,13 @@ sub attributes {
msgFail => '__badDomainName__',
default => 'example.com',
documentation => 'DNS domain',
flags => 'hp',
},
httpOnly => {
default => 1,
type => 'bool',
documentation => 'Enable httpOnly flag in cookie',
flags => 'hp',
},
securedCookie => {
type => 'select',
......@@ -672,6 +712,7 @@ sub attributes {
],
default => 0,
documentation => 'Cookie securisation method',
flags => 'hp',
},
# Notification
......@@ -767,6 +808,7 @@ sub attributes {
type => 'PerlModule',
default => 'Apache::Session::File',
documentation => 'Session backend module',
flags => 'hp',
},
globalStorageOptions => {
type => 'keyTextContainer',
......@@ -777,6 +819,7 @@ sub attributes {
'Lemonldap::NG::Common::Apache::Session::Generate::SHA256',
},
documentation => 'Session backend module options',
flags => 'hp',
},
localSessionStorage => {
type => 'PerlModule',
......@@ -1234,6 +1277,7 @@ sub attributes {
default => 'deny',
},
documentation => 'Virtualhost rules',
flags => 'h',
},
exportedHeaders => {
type => 'keyTextContainer',
......@@ -1255,6 +1299,7 @@ sub attributes {
}
},
documentation => 'Virtualhost headers',
flags => 'h',
},
post => {
type => 'postContainer',
......@@ -1301,6 +1346,70 @@ sub attributes {
type => 'int',
},
# SecureToken parameters
secureTokenAllowOnError => {
type => 'text',
documentation => 'Secure Token allow requests in error',
flags => 'h',
},
secureTokenAttribute => {
type => 'text',
documentation => 'Secure Token attribute',
flags => 'h',
default => 'uid',
},
secureTokenExpiration => {
type => 'text',
documentation => 'Secure Token expiration',
flags => 'h',
default => 60,
},
secureTokenHeader => {
type => 'text',
documentation => 'Secure Token header',
flags => 'h',
default => 'Auth-Token',
},
secureTokenMemcachedServers => {
type => 'text',
documentation => 'Secure Token Memcached servers',
flags => 'h',
default => '',
},
secureTokenUrls => {
type => 'text',
documentation => '',
flags => 'h',
default => '.*',
},
# Zimbra handler parameters
zimbraAccountKey => {
type => 'text',
flags => 'h',
documentation => 'Zimbra account session key',
},
zimbraBy => {
type => 'text',
flags => 'h',
documentation => 'Zimbra account type',
},
zimbraPreAuthKey => {
type => 'text',
flags => 'h',
documentation => 'Zimbra preauthentication key',
},
zimbraSsoUrl => {
type => 'text',
flags => 'h',
documentation => 'Zimbra local SSO URL pattern',
},
zimbraUrl => {
type => 'text',
flags => 'h',
documentation => 'Zimbra preauthentication URL',
},
# CAS IDP
casAttr => { type => 'text', },
casAttributes => { type => 'keyTextContainer', },
......
......@@ -31,6 +31,17 @@ my @notManagedAttributes = (
# PSGI/CGI protection (must be set in lemonldap-ng.ini)
'protection',
# SecureToken handler
'secureTokenAllowOnError', 'secureTokenAttribute', 'secureTokenExpiration',
'secureTokenHeader', 'secureTokenMemcachedServers', 'secureTokenUrls',
# Zimbra handler
'zimbraAccountKey', 'zimbraBy', 'zimbraPreAuthKey',
'zimbraSsoUrl', 'zimbraUrl',
# Other ini-only prms
'configStorage', 'status', 'localStorageOptions', 'localStorage',
);
# Words used either as attribute name and node title
......
......@@ -3,7 +3,11 @@ package Lemonldap::NG::Portal::2F::Engines::Default;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_ERROR
PE_NOTOKEN
PE_OK
PE_SENDRESPONSE
PE_TOKENEXPIRED
);
our $VERSION = '2.0.0';
......@@ -68,7 +72,7 @@ sub init {
# Store module
push @{ $self->{ $i ? 'sfRModules' : 'sfModules' } },
{ m => $m, r => $rule };
{ p => $prefix, m => $m, r => $rule };
}
else {
$self->logger->debug(' -> not enabled');
......@@ -76,6 +80,12 @@ sub init {
}
}
# Enable REST request only if more than 1 2F module is enabled
if ( @{ $self->{sfModules} } > 1 ) {
$self->addUnauthRoute( '2fchoice' => 'choice', ['POST'] );
$self->addUnauthRoute( '2fchoice' => 'redirect', ['GET'] );
}
return 1;
}
......@@ -110,17 +120,81 @@ sub run {
$req->sessionInfo->{_2fRealSession} = $req->id;
$req->sessionInfo->{_2fUrldc} = $req->urldc;
my $token = $self->ott->createToken( $req->sessionInfo );
delete $req->{authResult};
# If only one 2F is authorizated, display it
unless ($#am) {
my $res = $am[0]->run( $req, $token );
delete $req->{authResult} if ($res);
$req->authResult($res);
return $res;
}
# More than 1 2F has been found, display choice
# TODO
return PE_OK;
$self->logger->debug("Prepare 2F choice");
my $tpl = $self->p->sendHtml(
$req,
'2fchoice',
params => {
SKIN => $self->conf->{portalSkin},
TOKEN => $token,
MODULES => [ map { { CODE => $_->prefix, LOGO => $_->logo } } @am ],
}
);
$req->response($tpl);
return PE_SENDRESPONSE;
# TODO:
# - 2fchoice.tpl
# - choice() which launch choosenModule->run($req,$token)
# - add logos for 2F modules
}
sub choice {
my ( $self, $req ) = @_;
my $token;
# Restore session
unless ( $token = $req->param('token') ) {
$self->userLogger->error( $self->prefix . ' 2F access without token' );
$req->mustRedirect(1);
return $self->p->do( $req, [ sub { PE_NOTOKEN } ] );
}
my $session;
unless ( $session = $self->ott->getToken($token) ) {
$self->userLogger->info('Token expired');
return $self->p->do( $req, [ sub { PE_TOKENEXPIRED } ] );
}
$req->sessionInfo($session);
# New token
$token = $self->ott->createToken($session);
my $ch = $req->param('sf');
foreach my $m ( @{ $self->sfModules } ) {
if ( $m->{m}->prefix eq $ch ) {
my $res = $m->{m}->run( $req, $token );
$req->authResult($res);
return $self->p->do(
$req,
[
sub { $res }, 'controlUrl',
'buildCookie', @{ $self->p->afterDatas },
]
);
}
}
$self->userLogger->error('Bd 2F choice');
return $self->p->lmError( $req, 500 );
}
sub _redirect {
my ( $self, $req ) = @_;
my $arg = $req->env->{QUERY_STRING};
return [
302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ], []
];
}
1;
package Lemonldap::NG::Portal::2F::External2F;
package Lemonldap::NG::Portal::2F::Ext2F;
use strict;
use Mouse;
......
......@@ -19,6 +19,8 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor',
has prefix => ( is => 'ro', default => 'totp' );
has logo => ( is => 'rw', default => 'totp.png' );
sub init {
my ($self) = @_;
......
......@@ -25,6 +25,8 @@ has rule => ( is => 'rw' );
has prefix => ( is => 'ro', default => 'u' );
has logo => ( is => 'rw', default => 'u2f.png' );
sub init {
my ($self) = @_;
......
......@@ -118,7 +118,15 @@ sub _buildAuthLoop {
if ( $auth and $userDB and $passwordDB ) {
# Default URL
$url = ( defined $url ? $url .= $req->env->{'REQUEST_URI'} : '#' );
if ( defined $url
and not $self->p->checkXSSAttack( 'URI',
$req->env->{'REQUEST_URI'} ) )
{
$url .= $req->env->{'REQUEST_URI'};
}
else {
$url .= '#';
}
$self->logger->debug("Use URL $url");
# Options to store in the loop
......
......@@ -20,7 +20,7 @@ sub authProcess { qw(extractFormInfo getUser authenticate) }
sub sessionDatas {
qw(setAuthSessionInfo setSessionInfo setMacros setGroups setPersistentSessionInfo
setLocalGroups store buildCookie secondFactor);
setLocalGroups store secondFactor buildCookie);
}
# RESPONSE HANDLER
......
......@@ -26,6 +26,8 @@ has ott => (
has prefix => ( is => 'rw' );
has logo => ( is => 'rw', default => '2f.png' );
sub init {
my ($self) = @_;
$self->addUnauthRoute( $self->prefix . '2fcheck' => '_verify', ['POST'] );
......
......@@ -104,6 +104,7 @@
"changeKey": "Generate new key",
"changePwd":"غير كلمة المرور الخاصة بك",
"checkLastLogins":"تحقق من آخر تسجيلات دخول الخاصة بي",
"choose2f":"Choose your second factor",
"chooseApp":"اختر أحد التطبيقات المسموح لك بالدخول إليها",
"clickHere":"الرجاء الضغط هنا",
"closeSSO":"أغلق جلسة الدخول الموحد (سسو)",
......
......@@ -104,6 +104,7 @@
"changeKey": "Generate new key",
"changePwd":"Change your password",
"checkLastLogins":"Check my last logins",
"choose2f":"Choose your second factor",
"chooseApp":"Choose an application your are allowed to access to",
"clickHere":"Please click here",
"closeSSO":"Close your SSO session",
......
......@@ -104,6 +104,7 @@
"changeKey": "Generate new key",
"changePwd":"Change your password",
"checkLastLogins":"Check my last logins",
"choose2f":"Choose your second factor",
"chooseApp":"Choose an application your are allowed to access to",
"clickHere":"Please click here",
"closeSSO":"Close your SSO session",
......
......@@ -105,6 +105,7 @@
"changePwd":"Change your password",
"checkLastLogins":"Check my last logins",
"chooseApp":"Choose an application your are allowed to access to",
"choose2f":"Choose your second factor",
"clickHere":"Please click here",
"closeSSO":"Close your SSO session",
"code": "Code",
......
......@@ -104,6 +104,7 @@
"changeKey": "Générer une nouvelle clef",
"changePwd":"Changez votre mot de passe",
"checkLastLogins":"Voir mes dernières connexions",
"choose2f":"Choisissez votre second facteur",
"chooseApp":"Choisissez une application à laquelle vous êtes autorisé à accéder",
"clickHere":"Cliquez ici",
"closeSSO":"Fermer votre Session SSO",
......
......@@ -104,6 +104,7 @@
"changeKey": "Generate new key",
"changePwd":"Cambia la tua password",
"checkLastLogins":"Controllare i miei ultimi accessi",
"choose2f":"Choose your second factor",
"chooseApp":"Scegli un'applicazione alla quale ti è consentito l'accesso",
"clickHere":"Per favore clicka qui",
"closeSSO":"Chiudi la sessione SSO",
......
......@@ -104,6 +104,7 @@
"changeKey": "Generate new key",
"changePwd":"Change your password",
"checkLastLogins":"Check my last logins",
"choose2f":"Choose your second factor",
"chooseApp":"Choose an application your are allowed to access to",
"clickHere":"Please click here",
"closeSSO":"Close your SSO session",
......
......@@ -104,6 +104,7 @@
"changeKey": "Generate new key",
"changePwd":"Change your password",
"checkLastLogins":"Check my last logins",
"choose2f":"Choose your second factor",
"chooseApp":"Choose an application your are allowed to access to",
"clickHere":"Please click here",
"closeSSO":"Close your SSO session",
......
......@@ -104,6 +104,7 @@
"changeKey": "Generate new key",
"changePwd":"Change your password",
"checkLastLogins":"Check my last logins",
"choose2f":"Choose your second factor",
"chooseApp":"Choose an application your are allowed to access to",
"clickHere":"Please click here",
"closeSSO":"Close your SSO session",
......
......@@ -104,6 +104,7 @@
"changeKey": "Generate new key",
"changePwd":"Thay đổi mật khẩu của bạn",
"checkLastLogins":"Kiểm tra lần đăng nhập cuối cùng của bạn",
"choose2f":"Choose your second factor",
"chooseApp":"Chọn một ứng dụng bạn được phép truy cập vào",
"clickHere":"Vui lòng nhấp vào đây",
"closeSSO":"Đóng phiên SSO của bạn",
......
<TMPL_INCLUDE NAME="header.tpl">
<div class="container">
<div class="message message-positive alert" trspan="choose2f"></div>
<div class="buttons">
<form action="/2fchoice" method="POST">
<input type="hidden" name="token" id="token" value="<TMPL_VAR NAME="TOKEN">" />
<TMPL_LOOP NAME="MODULES">
<button type="submit" name="sf" value="<TMPL_VAR NAME="CODE">">
<img src="<TMPL_VAR NAME="STATIC_PREFIX"><TMPL_VAR NAME="SKIN">/<TMPL_VAR NAME="LOGO">" />
</button>
</TMPL_LOOP>
</form>
</div>
</div>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp;
<span trspan="goToPortal">Go to portal</span>
</a>
</div>
<TMPL_INCLUDE NAME="footer.tpl">
......@@ -30,7 +30,7 @@
</main>
<div class="buttons">
<a id="goback" href="" class="btn btn-primary" role="button">
<a id="goback" href="<TMPL_VAR NAME="PORTAL_URL"><TMPL_IF NAME="AUTH_URL">/?url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp;
<span trspan="goToPortal">Go to portal</span>
</a>
......
......@@ -28,6 +28,7 @@
</main>
<div class="buttons">
<a id="goback" href="<TMPL_VAR NAME="PORTAL_URL"><TMPL_IF NAME="AUTH_URL">/?url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>" class="btn btn-primary" role="button">
<a id="goback" href="" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp;
<span trspan="goToPortal">Go to portal</span>
......
#!/usr/bin/perl
use strict;
use JSON;
require './lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm';
require './lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm';
my $rmg =