Forbid browsers to store users password & Improve unit tests (#1913)

This commit is contained in:
Christophe Maudoux 2019-09-08 19:26:09 +02:00
parent 94877793d4
commit 132f42d44c
21 changed files with 63 additions and 11 deletions

View File

@ -121,6 +121,10 @@ License: CC-3
Comment: This work, "CustomAuth.png", is a derivative of
"Noun project 1162.svg" by Christopher T. Howlett, under CC-BY-3.0.
Files: lemonldap-ng-portal/site/htdocs/static/common/fonts/password.ttf
Copyright: 2007, the Tap2Play Team, https://git.tap2play.org.au/tap2play/web/tree/dev/fonts
License: Expat
Files: lemonldap-ng-portal/site/htdocs/static/common/backgrounds/*
Copyright: Various artists
License: CC-BY-NC-ND-3.0 or GFDL-1.3

View File

@ -24,7 +24,7 @@ use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
use constant APPLYSECTION => "apply";
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node)|S(?:erviceMetaDataAuthnContext|torageOptions))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions)|A(?:ppMetaData(?:(?:ExportedVar|Option)s|Node)|ttributes))|(?:ustomAddParam|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|ingle(?:Session(?:UserByIP)?|(?:UserBy)?IP)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|howLanguages|slByAjax)|o(?:idc(?:ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|RPMetaDataOptions(?:LogoutSessionRequired|BypassConsent|RequirePKCE|Public)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:setPassword|gister)|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:PersistentInfo|EmptyValues))?|State|XSS)|o(?:ntextSwitchingStopWithLogout|rsEnabled)|da)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?)?|y(?:Deleted|Other))|AjaxHook)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|d(?:isablePersistentStorage|biDynamicHashEnabled|ontCompactConf)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|rest(?:(?:Session|Config)Server|ExportSecretKeys)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs)|bruteForceProtection)$/;
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|ingle(?:Session(?:UserByIP)?|(?:UserBy)?IP)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|howLanguages|slByAjax)|o(?:idc(?:ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|RPMetaDataOptions(?:LogoutSessionRequired|BypassConsent|RequirePKCE|Public)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:setPassword|gister)|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:PersistentInfo|EmptyValues))?|State|XSS)|o(?:ntextSwitchingStopWithLogout|rsEnabled)|da)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?)?|y(?:Deleted|Other))|AjaxHook)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|d(?:isablePersistentStorage|biDynamicHashEnabled|ontCompactConf)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|rest(?:(?:Session|Config)Server|ExportSecretKeys)|br(?:owsersDontStorePassword|uteForceProtection)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );

View File

@ -605,6 +605,10 @@ sub attributes {
'default' => 'TOTP,U2F,Yubikey',
'type' => 'text'
},
'browsersDontStorePassword' => {
'default' => 0,
'type' => 'bool'
},
'bruteForceProtection' => {
'default' => 0,
'type' => 'bool'

View File

@ -865,6 +865,11 @@ sub attributes {
default => '^[\w\.\-@]+$',
documentation => 'Regular expression to validate login',
},
browsersDontStorePassword => {
default => 0,
type => 'bool',
documentation => 'Avoid browsers to store users password',
},
useRedirectOnError => {
type => 'bool',
default => 1,

View File

@ -862,6 +862,7 @@ sub tree {
help => 'security.html#configure_security_settings',
nodes => [
'userControl',
'browsersDontStorePassword',
'portalForceAuthn',
'portalForceAuthnInterval',
'key',

View File

@ -94,6 +94,7 @@
"badVariableName":"اسم المتغيرة خاطئ",
"blackList":"القائمة السوداء",
"browse":"تصفح",
"browsersDontStorePassword":"Avoid browsers to store users password",
"browserIdAuthnLevel":"مستوى إثبات الهوية",
"browserIdAutoLogin":"تسجيل الدخول التلقائي",
"browserIdBackgroundColor":"لون الخلفية",

View File

@ -94,6 +94,7 @@
"badVariableName":"Bad variable name",
"blackList":"Black list",
"browse":"Browse",
"browsersDontStorePassword":"Avoid browsers to store users password",
"browserIdAuthnLevel":"Authentication level",
"browserIdAutoLogin":"Automatic login",
"browserIdBackgroundColor":"Background color",

View File

@ -94,6 +94,7 @@
"badVariableName":"Bad variable name",
"blackList":"Black list",
"browse":"Browse",
"browsersDontStorePassword":"Avoid browsers to store users password",
"browserIdAuthnLevel":"Authentication level",
"browserIdAutoLogin":"Automatic login",
"browserIdBackgroundColor":"Background color",

View File

@ -94,6 +94,7 @@
"badVariableName":"Mauvais nom de variable",
"blackList":"Liste noire",
"browse":"Naviguer",
"browsersDontStorePassword":"Interdire aux navigateurs de sauvegarder le mot de passe",
"browserIdAuthnLevel":"Niveau d'authentification",
"browserIdAutoLogin":"Authentification automatique",
"browserIdBackgroundColor":"Couleur d'arrière plan",

View File

@ -94,6 +94,7 @@
"badVariableName":"Nome variabile errato",
"blackList":"Black list",
"browse":"Naviga",
"browsersDontStorePassword":"Avoid browsers to store users password",
"browserIdAuthnLevel":"Livello di autenticazione",
"browserIdAutoLogin":"Login automatico",
"browserIdBackgroundColor":"Colore di sfondo",

View File

@ -94,6 +94,7 @@
"badVariableName":"Tên biến không hợp lệ",
"blackList":"Danh sách đen",
"browse":"Duyệt",
"browsersDontStorePassword":"Avoid browsers to store users password",
"browserIdAuthnLevel":"Mức xác thực",
"browserIdAutoLogin":"Đăng nhập tự động",
"browserIdBackgroundColor":"Màu nền",

View File

@ -94,6 +94,7 @@
"badVariableName":"无效的 variable 名称",
"blackList":"黑名单",
"browse":"浏览",
"browsersDontStorePassword":"Avoid browsers to store users password",
"browserIdAuthnLevel":"认证等级",
"browserIdAutoLogin":"自动登录",
"browserIdBackgroundColor":"背景颜色",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -296,6 +296,7 @@ sub display {
AUTH_ERROR_TYPE => $req->error_type,
AUTH_URL => $req->{data}->{_url},
LOGIN => $login,
DONT_STORE_PASSWORD => $self->conf->{browsersDontStorePassword},
CHECK_LOGINS => $self->conf->{portalCheckLogins},
ASK_LOGINS => $req->param('checkLogins') || 0,
DISPLAY_RESETPASSWORD => $self->conf->{portalDisplayResetPassword},

View File

@ -163,3 +163,15 @@ div.oidc_consent_message > ul {
.progress-bar-animated {
width: 100%;
}
input.key {
font-family: 'password';
width: 100px;
}
@font-face {
font-family: 'password';
/*font-style: normal;*/
/*font-weight: 400;*/
src: url(/static/common/fonts/password.ttf);
}

View File

@ -1 +1 @@
html,body{height:100%;background:radial-gradient(circle at 50% 0,#fff 0,#ddd 100%) no-repeat scroll 0 0 #ddd}#wrap{min-height:100%;height:auto;margin:0 auto -80px;padding:20px 0 80px}#footer{height:80px;background-color:#fff;background-color:rgba(255,255,255,0.9);text-align:center;padding-top:10px;overflow:hidden}#header img{background-color:#fff;background-color:rgba(255,255,255,0.8);margin-bottom:20px}.card,.navbar-light{background-color:#fff;background-color:rgba(255,255,255,0.9);background-image:none}.login,.password{text-align:center;padding:20px}div.form{margin:0 auto;max-width:330px}div.actions{margin:10px 0 0 0}div.actions a{margin-top:10px}.buttons{text-align:center;margin:10px 0 0 0;cursor:pointer}.btn{white-space:normal}.btn span.fa{padding-right:8px}li.ui-state-active{background-color:#fafafa;background-color:rgba(250,250,250,0.9)}#appslist,#password,#loginHistory,#logout,#oidcConsents{margin-top:20px}div.category{margin:10px 0;cursor:grab}div.application{margin:5px 0;overflow:hidden}div.application a,div.application a:hover{text-decoration:none}p.notifCheck label{margin-left:5px;margin-top:3px;display:inline-block}img.langicon{cursor:pointer}button.idploop{max-width:300px}button.idploop img{max-height:30px}div.oidc_consent_message>ul{text-align:left;list-style:circle}@media(min-width:768px){div.application{height:80px}div.application h4.appname{margin:0}#wrap{margin:0 auto -60px}#footer{height:60px}}.hiddenFrame{border:0;display:hidden;margin:0}.noborder{border:0}.max{width:100%}.link{cursor:pointer}.nodecor:hover,.nodecor:active.nodecor:focus{text-decoration:none}.fa.icon-blue{color:blue}.progress-bar-animated{width:100%}
html,body{height:100%;background:radial-gradient(circle at 50% 0,#fff 0,#ddd 100%) no-repeat scroll 0 0 #ddd}#wrap{min-height:100%;height:auto;margin:0 auto -80px;padding:20px 0 80px}#footer{height:80px;background-color:#fff;background-color:rgba(255,255,255,0.9);text-align:center;padding-top:10px;overflow:hidden}#header img{background-color:#fff;background-color:rgba(255,255,255,0.8);margin-bottom:20px}.card,.navbar-light{background-color:#fff;background-color:rgba(255,255,255,0.9);background-image:none}.login,.password{text-align:center;padding:20px}div.form{margin:0 auto;max-width:330px}div.actions{margin:10px 0 0 0}div.actions a{margin-top:10px}.buttons{text-align:center;margin:10px 0 0 0;cursor:pointer}.btn{white-space:normal}.btn span.fa{padding-right:8px}li.ui-state-active{background-color:#fafafa;background-color:rgba(250,250,250,0.9)}#appslist,#password,#loginHistory,#logout,#oidcConsents{margin-top:20px}div.category{margin:10px 0;cursor:grab}div.application{margin:5px 0;overflow:hidden}div.application a,div.application a:hover{text-decoration:none}p.notifCheck label{margin-left:5px;margin-top:3px;display:inline-block}img.langicon{cursor:pointer}button.idploop{max-width:300px}button.idploop img{max-height:30px}div.oidc_consent_message>ul{text-align:left;list-style:circle}@media(min-width:768px){div.application{height:80px}div.application h4.appname{margin:0}#wrap{margin:0 auto -60px}#footer{height:60px}}.hiddenFrame{border:0;display:hidden;margin:0}.noborder{border:0}.max{width:100%}.link{cursor:pointer}.nodecor:hover,.nodecor:active.nodecor:focus{text-decoration:none}.fa.icon-blue{color:blue}.progress-bar-animated{width:100%}input.key{font-family:'password';width:100px}@font-face{font-family:'password';src:url(/static/common/fonts/password.ttf)}

View File

@ -17,7 +17,11 @@
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-lock"></i> </span>
</div>
<input name="password" type="password" class="form-control" trplaceholder="password" required aria-required="true"/>
<TMPL_IF NAME="DONT_STORE_PASSWORD">
<input name="password" type="text" class="form-control key" trplaceholder="password" autocomplete="off" required aria-required="true"/>
<TMPL_ELSE>
<input name="password" type="password" class="form-control" trplaceholder="password" required aria-required="true"/>
</TMPL_IF>
</div>
<TMPL_IF NAME=CAPTCHA_SRC>

View File

@ -6,7 +6,7 @@ require 't/test-lib.pm';
my $res;
my $maintests = 16;
my $maintests = 17;
SKIP: {
eval 'use GD::SecurityImage;use Image::Magick;';
if ($@) {
@ -15,11 +15,12 @@ SKIP: {
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
useSafeJail => 1,
loginHistoryEnabled => 1,
captcha_login_enabled => 1,
portalMainLogo => 'common/logos/logo_llng_old.png',
logLevel => 'error',
useSafeJail => 1,
browsersDontStorePassword => 1,
loginHistoryEnabled => 1,
captcha_login_enabled => 1,
portalMainLogo => 'common/logos/logo_llng_old.png',
}
}
);
@ -31,6 +32,12 @@ SKIP: {
ok( $res = $client->_get( '/', accept => 'text/html' ), 'Unauth request' );
my ( $host, $url, $query ) = expectForm( $res, '#', undef, 'token' );
ok(
$res->[2]->[0] =~
m%<input name="password" type="text" class="form-control key" trplaceholder="password" autocomplete="off" required aria-required="true"/>%,
'Password: Found text input'
);
$query =~ s/.*\btoken=([^&]+).*/token=$1/;
my $token;
ok( $token = $1, ' Token value is defined' );

View File

@ -21,6 +21,13 @@ ok( $res = $client->_get( '/', accept => 'text/html' ), 'Unauth request' );
count(1);
my ( $host, $url, $query ) = expectForm( $res, '#', undef, 'token' );
ok(
$res->[2]->[0] =~
m%<input name="password" type="password" class="form-control" trplaceholder="password" required aria-required="true"/>%,
'Password: Found password input'
);
count(1);
$query =~ s/.*\b(token=[^&]+).*/$1/;
# Try to auth without token