Skip to content

Commit

Permalink
Item14414: Add support for enable/disable passwords.
Browse files Browse the repository at this point in the history
 - Htpasswor now loads "commented" accounts but marks them disabled.
 - Login will reject any disabled account
 - USERINFO macro displays enabled/disabled state.
  • Loading branch information
gac410 committed Jun 19, 2017
1 parent 8a31d27 commit a3ed1e8
Show file tree
Hide file tree
Showing 13 changed files with 368 additions and 93 deletions.
24 changes: 23 additions & 1 deletion TopicUserMappingContrib/lib/Foswiki/Users/TopicUserMapping.pm
Expand Up @@ -1585,6 +1585,28 @@ sub passwordError {

=begin TML
---++ ObjectMethod userEnabled( $login, $enabled ) -> $boolean
Finds if the password is enabled for the given user.
Returns 1 on success, undef on failure.
=cut

sub userEnabled {
my ( $this, $login, $enabled ) = @_;

# If we don't have a PasswordManager and use TemplateLogin, always allow login
return 1
if ( $Foswiki::cfg{PasswordManager} eq 'none'
&& $Foswiki::cfg{LoginManager} eq
'Foswiki::LoginManager::TemplateLogin' );

return $this->{passwords}->userEnabled( $login, $enabled );
}

=begin TML
---++ ObjectMethod validateRegistrationField($field, $value ) -> $string
This method is called for every field submitted during registration. It is also used
Expand Down Expand Up @@ -1788,7 +1810,7 @@ sub _expandUserList {
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Copyright (C) 2008-2010 Foswiki Contributors. Foswiki Contributors
Copyright (C) 2008-2017 Foswiki Contributors. Foswiki Contributors
are listed in the AUTHORS file in the root of this distribution.
NOTE: Please extend that file, not this notice.
Expand Down
69 changes: 63 additions & 6 deletions UnitTestContrib/test/unit/PasswordTests.pm
Expand Up @@ -327,26 +327,37 @@ crypt-md5::crypt-md5@example.com
DONE
$this->assert( close($fh) );

foreach
my $algo ( 'apache-md5', 'htdigest-md5', 'crypt', 'sha1', 'crypt-md5' )
{
foreach my $algo ( 'apache-md5', 'crypt', 'sha1', 'crypt-md5' ) {
$Foswiki::cfg{Htpasswd}{Encoding} = $algo;
$impl = Foswiki::Users::HtPasswdUser->new( $this->{session} );
$impl->ClearCache() if $impl->can('ClearCache');

foreach my $user ( 'crypt', 'apache-md5', 'sha1', 'htdigest-md5',
'crypt-md5' )
{
foreach my $user ( 'crypt', 'apache-md5', 'sha1', 'crypt-md5' ) {
$this->assert( !$impl->checkPassword( $user, '' ) );
$this->assert( $impl->userEnabled($user) );
}
$impl->finish();
}

$impl->finish();

# SMELL: With autodetect disabled, htdigest algorithm can't handle passwords
# stored with other formats.
$Foswiki::cfg{Htpasswd}{AutoDetect} = 0;
$Foswiki::cfg{Htpasswd}{Encoding} = 'htdigest-md5';
$impl = Foswiki::Users::HtPasswdUser->new( $this->{session} );
$impl->ClearCache() if $impl->can('ClearCache');
$this->assert( !$impl->checkPassword( 'htdigest-md5', '' ) );
$this->assert( $impl->userEnabled('htdigest-md5') );

# Verify that each algorithm can reset an empty password entry
# But need to autodetect to not corrupt existing entries
$Foswiki::cfg{Htpasswd}{AutoDetect} = 1;
foreach
my $user ( 'crypt', 'apache-md5', 'sha1', 'htdigest-md5', 'crypt-md5' )
{
$Foswiki::cfg{Htpasswd}{Encoding} = $user;
$impl->finish();
$impl = Foswiki::Users::HtPasswdUser->new( $this->{session} );

my $added = $impl->setPassword( $user, "pw$user", 1 );
Expand All @@ -357,12 +368,58 @@ DONE

# Verify that the passwords were reset
$Foswiki::cfg{Htpasswd}{AutoDetect} = 1;
$impl->finish();
$impl = Foswiki::Users::HtPasswdUser->new( $this->{session} );
$impl->ClearCache() if $impl->can('ClearCache');
foreach my $user ( 'crypt', 'apache-md5', 'sha1', 'crypt-md5' ) {
$this->assert( $impl->checkPassword( $user, "pw$user" ),
"Failure for $user" );
}

# Make sure file is detected as modified.
sleep 2;

open( $fh, '>:encoding(utf-8)', "$Foswiki::cfg{TempfileDir}/junkpasswd" )
|| die "Unable to open \n $! \n\n ";
print $fh <<'DONE';
#crypt::crypt@example.com
#apache-md5::apache-md5@example.com
#sha1::sha1@example.com
#htdigest-md5:MyNewRealm::htdigest-md5@example.com
#crypt-md5::crypt-md5@example.com
DONE
$this->assert( close($fh) );

# Verify that disabled entries are properly detected.
foreach
my $algo ( 'apache-md5', 'crypt', 'sha1', 'crypt-md5', 'htdigest-md5' )
{
$Foswiki::cfg{Htpasswd}{Encoding} = $algo;
$impl = Foswiki::Users::HtPasswdUser->new( $this->{session} );
$impl->ClearCache() if $impl->can('ClearCache');

foreach my $user ( 'crypt', 'apache-md5', 'sha1',
'crypt-md5', 'htdigest-md5' )
{
$this->assert( !$impl->checkPassword( $user, '' ) );
$this->assert( !$impl->userEnabled($user) );
}
$impl->finish();
}

foreach
my $user ( 'crypt', 'apache-md5', 'sha1', 'htdigest-md5', 'crypt-md5' )
{
$Foswiki::cfg{Htpasswd}{Encoding} = $user;
$impl->finish();
$impl = Foswiki::Users::HtPasswdUser->new( $this->{session} );

my $enabled = $impl->userEnabled( $user, 1 );
$this->assert($enabled);
}

#dumpFile();

return;
}

Expand Down
3 changes: 2 additions & 1 deletion core/data/System/VarUSERINFO.txt
@@ -1,4 +1,4 @@
%META:TOPICINFO{author="ProjectContributor" date="1456368117" format="1.1" version="1"}%
%META:TOPICINFO{author="ProjectContributor" date="1497836887" format="1.1" version="1"}%
%META:TOPICPARENT{name="Macros"}%
---+ USERINFO -- retrieve details about a user
---++ Parameters
Expand All @@ -13,6 +13,7 @@ Format tokens that can be used in =format=:
| =$groups= (*) | Comma separated list of group membership. Currently only expands for users |
| =$isadmin= (*) | Has admin privileges (expands to =true= or =false=) |
| =$isgroup= | Is a group (expands to =true= or =false=) | |
| =$isenabled= | Returns true if the account is enabled. | |
%T% Tokens flagged '(*)' are considered private and are hidden from other users by default.%BR%
The [[FormatTokens][standard format tokens]] are also supported.
---++ Examples
Expand Down
14 changes: 13 additions & 1 deletion core/lib/Foswiki/LoginManager.pm
Expand Up @@ -528,6 +528,18 @@ sub loadSession {
# We should have a user at this point; or $defaultUser if there
# was no better information available.

if ( defined $this->{_cgisession}
&& $pwchecker
&& !$pwchecker->userEnabled($authUser) )
{
$this->{_cgisession}->delete();
$this->{_cgisession}->flush();
$this->{_cgisession} = undef;
$this->_delSessionCookieFromResponse();

$authUser = $this->redirectToLoggedOutUrl( $authUser, $defaultUser );
}

# is this a logout?
if (
( $authUser && $authUser ne $Foswiki::cfg{DefaultUserLogin} )
Expand Down Expand Up @@ -1604,7 +1616,7 @@ sub removeUserSessions {
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Copyright (C) 2008-2015 Foswiki Contributors. Foswiki Contributors
Copyright (C) 2008-2017 Foswiki Contributors. Foswiki Contributors
are listed in the AUTHORS file in the root of this distribution.
NOTE: Please extend that file, not this notice.
Expand Down
158 changes: 88 additions & 70 deletions core/lib/Foswiki/LoginManager/TemplateLogin.pm
Expand Up @@ -195,93 +195,111 @@ sub login {
my $error = '';

if ($loginName) {
my $validation = $users->checkPassword( $loginName, $loginPass );
$error = $users->passwordError($loginName);

if ( !$validation
&& $Foswiki::cfg{TemplateLogin}{AllowLoginUsingEmailAddress}
&& ( $loginName =~ $Foswiki::regex{emailAddrRegex} ) )
{

# try email addresses if it is one
my $cuidList = $users->findUserByEmail($loginName);
foreach my $cuid (@$cuidList) {
my $login = $users->getLoginName($cuid);

$validation = $users->checkPassword( $login, $loginPass );
if ($validation) {
$loginName = $login;
last;
}
}
}

if ($validation) {

# SUCCESS our user is authenticated. Note that we may already
# have been logged in by the userLoggedIn call in loadSession,
# because the username-password URL params are the same as
# the params passed to this script, and they will be used
# in loadSession if no other user info is available.
$this->userLoggedIn($loginName);
if ( !$users->userEnabled($loginName) ) {
$session->{response}->status(200);
$session->logger->log(
{
level => 'info',
action => 'login',
webTopic => $web . '.' . $topic,
extra => "AUTHENTICATION SUCCESS - $loginName - "
extra => "AUTHENTICATION DENIED - $loginName - Disabled",
}
);
$banner = $session->templates->expandTemplate('DISABLED_USER');
}
else {

my $validation = $users->checkPassword( $loginName, $loginPass );
$error = $users->passwordError($loginName);

# remove the sudo param - its only to tell TemplateLogin
# that we're using BaseMapper..
$query->delete('sudo');
if ( !$validation
&& $Foswiki::cfg{TemplateLogin}{AllowLoginUsingEmailAddress}
&& ( $loginName =~ $Foswiki::regex{emailAddrRegex} ) )
{

$this->{_cgisession}->param( 'VALIDATION', $validation )
if $this->{_cgisession};
if ( !$origurl || $origurl eq $query->url() ) {
$origurl = $session->getScriptUrl( 0, 'view', $web, $topic );
# try email addresses if it is one
my $cuidList = $users->findUserByEmail($loginName);
foreach my $cuid (@$cuidList) {
my $login = $users->getLoginName($cuid);

$validation = $users->checkPassword( $login, $loginPass );
if ($validation) {
$loginName = $login;
last;
}
}
}
else {

# Unpack params encoded in the origurl and restore them
# to the query. If they were left in the query string they
# would be lost if we redirect with passthrough.
# First extract the params, ignoring any trailing fragment.
if ( $origurl =~ s/\?([^#]*)// ) {
foreach my $pair ( split( /[&;]/, $1 ) ) {
if ( $pair =~ m/(.*?)=(.*)/ ) {
$query->param( $1, TAINT($2) );
if ($validation) {

# SUCCESS our user is authenticated. Note that we may already
# have been logged in by the userLoggedIn call in loadSession,
# because the username-password URL params are the same as
# the params passed to this script, and they will be used
# in loadSession if no other user info is available.
$this->userLoggedIn($loginName);
$session->logger->log(
{
level => 'info',
action => 'login',
webTopic => $web . '.' . $topic,
extra => "AUTHENTICATION SUCCESS - $loginName - "
}
);

# remove the sudo param - its only to tell TemplateLogin
# that we're using BaseMapper..
$query->delete('sudo');

$this->{_cgisession}->param( 'VALIDATION', $validation )
if $this->{_cgisession};
if ( !$origurl || $origurl eq $query->url() ) {
$origurl =
$session->getScriptUrl( 0, 'view', $web, $topic );
}
else {

# Unpack params encoded in the origurl and restore them
# to the query. If they were left in the query string they
# would be lost if we redirect with passthrough.
# First extract the params, ignoring any trailing fragment.
if ( $origurl =~ s/\?([^#]*)// ) {
foreach my $pair ( split( /[&;]/, $1 ) ) {
if ( $pair =~ m/(.*?)=(.*)/ ) {
$query->param( $1, TAINT($2) );
}
}
}

# Restore the action too
$query->action($origaction) if $origaction;
}

# Restore the action too
$query->action($origaction) if $origaction;
# Restore the method used on origUrl so if it was a GET, we
# get another GET.
$query->method($origmethod);
$session->redirect( $origurl, 1 );
return;
}
else {

# Restore the method used on origUrl so if it was a GET, we
# get another GET.
$query->method($origmethod);
$session->redirect( $origurl, 1 );
return;
}
else {

# Tasks:Item1029 After much discussion, the 403 code is not
# used for authentication failures. RFC states: "Authorization
# will not help and the request SHOULD NOT be repeated" which
# is not the situation here.
$session->{response}->status(200);
$session->logger->log(
{
level => 'info',
action => 'login',
webTopic => $web . '.' . $topic,
extra => "AUTHENTICATION FAILURE - $loginName - ",
}
);
$banner = $session->templates->expandTemplate('UNRECOGNISED_USER');
# Tasks:Item1029 After much discussion, the 403 code is not
# used for authentication failures. RFC states: "Authorization
# will not help and the request SHOULD NOT be repeated" which
# is not the situation here.
$session->{response}->status(200);
$session->logger->log(
{
level => 'info',
action => 'login',
webTopic => $web . '.' . $topic,
extra => "AUTHENTICATION FAILURE - $loginName - ",
}
);
$banner =
$session->templates->expandTemplate('UNRECOGNISED_USER');
}
}
}
else {
Expand Down Expand Up @@ -340,7 +358,7 @@ sub login {
__END__
Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Copyright (C) 2008-2015 Foswiki Contributors. All Rights Reserved.
Copyright (C) 2008-2017 Foswiki Contributors. All Rights Reserved.
Foswiki Contributors are listed in the AUTHORS file in the root
of this distribution. NOTE: Please extend that file, not this notice.
Expand Down
5 changes: 5 additions & 0 deletions core/lib/Foswiki/Macros/USERINFO.pm
Expand Up @@ -89,6 +89,11 @@ my %USERINFO_tokens = (
my ( $this, $user ) = @_;

return $this->{users}->isGroup($user) ? 'true' : 'false';
},
isenabled => sub {
my ( $this, $user ) = @_;

return $this->{users}->userEnabled($user) ? 'true' : 'false';
}
);
my $USERINFO_tokenregex = join( '|', keys %USERINFO_tokens );
Expand Down

0 comments on commit a3ed1e8

Please sign in to comment.