Permalink
Browse files

Item10962: Enhance htpasswd support

 - Permit migration between encoding types
 - Add support for Apache's md5 (apr1)

git-svn-id: http://svn.foswiki.org/branches/Release01x01@12213 0b4bb1d4-4e5a-0410-9cc4-b2b747904278
  • Loading branch information...
GeorgeClark GeorgeClark
GeorgeClark authored and GeorgeClark committed Jul 25, 2011
1 parent 36a8e18 commit 54914b0e74df6034579c91d8b9d515a63d399815
@@ -35,16 +35,18 @@ sub tear_down {
my $users1 = {
alligator => { pass => 'hissss', emails => 'ally@masai.mara' },
bat => { pass => 'ultrasonic squeal', emails => 'bat@belfry' },
budgie => { pass => 'tweet', emails => 'budgie@flock;budge@oz' },
lion => { pass => 'roar', emails => 'lion@pride' },
mole => { pass => '', emails => 'mole@hill' }
budgie => { pass => 'tweet', emails => 'budgie@flock;budge@oz' },
lion => { pass => 'roar', emails => 'lion@pride' },
dodo => { pass => '3zmVlgI9', emails => 'dodo@extinct' },
mole => { pass => '', emails => 'mole@hill' }
};
my $users2 = {
alligator => { pass => 'gnu', emails => $users1->{alligator}->{emails} },
bat => { pass => 'moth', emails => $users1->{bat}->{emails} },
budgie => { pass => 'millet', emails => $users1->{budgie}->{emails} },
lion => { pass => 'antelope', emails => $users1->{lion}->{emails} },
dodo => { pass => 'b2rd', emails => $users1->{dodo}->{emails} },
mole => { pass => 'earthworm', emails => $users1->{mole}->{emails} },
};
@@ -63,8 +65,11 @@ sub doTests {
$encrapted{$user} = $impl->fetchPass($user);
$this->assert_null( $impl->error() );
$this->assert( $encrapted{$user} );
$this->assert_str_equals( $encrapted{$user},
$impl->encrypt( $user, $users1->{$user}->{pass} ) );
$this->assert_str_equals(
$encrapted{$user},
$impl->encrypt( $user, $users1->{$user}->{pass} ),
"fails for $user"
);
$this->assert_str_equals( $users1->{$user}->{emails},
join( ";", $impl->getEmails($user) ) );
}
@@ -168,6 +173,192 @@ sub TODO_test_htpasswd_plain {
$this->doTests($impl);
}
sub test_htpasswd_auto {
my $this = shift;
foreach my $m (qw( Digest::SHA Crypt::PasswdMD5 )) {
eval "use $m";
if ($@) {
my $mess = $@;
$mess =~ s/\(\@INC contains:.*$//s;
$this->expect_failure();
$this->annotate("AUTO TESTS WILL FAIL: missing $m");
}
}
$Foswiki::cfg{AuthRealm} = 'MyNewRealmm';
$Foswiki::cfg{Htpasswd}{AutoDetect} = 1;
my %encrapted;
my %encoded;
my $impl = new Foswiki::Users::HtPasswdUser( $this->{session} );
# The following lines were generated with the apache htdigest and htpasswd command
# Used to verify the encode autodetect feature.
open( my $fh, '>', "$Foswiki::cfg{TempfileDir}/junkpasswd" )
|| die "Unable to open \n $! \n\n ";
print $fh <<'DONE';
alligator:njQ4t57Dts41s
bat:$apr1$9/PfK37z$HrNORnyJefA2ex4nWLOoR1
budgie:{SHA}1pqeQCvCHCfCrnFA8mTGYna/DV0=
dodo:$1$pUXqkX97$zqxdNSnpusVmoB.B.aUhB/:dodo@extinct
lion:MyNewRealmm:3e60f5f16dc3b8658879d316882a3f00
mole::mole@hill
DONE
#mole:$1$GfAYYH9N$mEiibRtbtp1177trZgAV00:mole@hill
close($fh);
# First try - no emails in file
# check it
foreach my $user ( sort keys %$users1 ) {
$this->assert( $impl->checkPassword( $user, $users1->{$user}->{pass} ),
"Failure for $user" );
( $encrapted{$user}, $encoded{$user} ) = $impl->fetchPass($user);
if ( $encrapted{$user} ) {
$this->assert_str_equals(
$encrapted{$user},
$impl->encrypt(
$user, $users1->{$user}->{pass},
0, $encoded{$user}
),
"Failure for $user"
);
}
}
return;
$impl = new Foswiki::Users::HtPasswdUser( $this->{session} );
# Test again with email addresses present
open( $fh, '>', "$Foswiki::cfg{TempfileDir}/junkpasswd" )
|| die "Unable to open \n $! \n\n ";
print $fh <<'DONE';
alligator:njQ4t57Dts41s:ally@masai.mara
bat:$apr1$9/PfK37z$HrNORnyJefA2ex4nWLOoR1:bat@belfry
budgie:{SHA}1pqeQCvCHCfCrnFA8mTGYna/DV0=:budgie@flock;budge@oz
dodo:$1$pUXqkX97$zqxdNSnpusVmoB.B.aUhB/:dodo@extinct
lion:MyNewRealmm:3e60f5f16dc3b8658879d316882a3f00:lion@pride
mole::mole@hill
DONE
#mole:$1$GfAYYH9N$mEiibRtbtp1177trZgAV00:mole@hill
close($fh);
# check it
foreach my $user ( sort keys %$users1 ) {
$this->assert( $impl->checkPassword( $user, $users1->{$user}->{pass} ),
"Failure for $user" );
( $encrapted{$user}, $encoded{$user} ) = $impl->fetchPass($user);
if ( $encrapted{$user} ) {
$this->assert_str_equals(
$encrapted{$user},
$impl->encrypt(
$user, $users1->{$user}->{pass},
0, $encoded{$user}
),
"Failure for $user"
);
}
}
#dumpFile();
# force-change them to users2 password, Verify emails have survived.
foreach my $user ( sort keys %$users1 ) {
my $added = $impl->setPassword(
$user,
$users2->{$user}->{pass},
$users1->{$user}->{pass}
);
$this->assert_null( $impl->error() );
$this->assert_str_not_equals( $encrapted{$user},
$impl->fetchPass($user) );
$this->assert_null( $impl->error() );
$this->assert_str_equals( $users1->{$user}->{emails},
join( ";", $impl->getEmails($user) ) );
}
$Foswiki::cfg{Htpasswd}{Encoding} = 'md5';
$impl = new Foswiki::Users::HtPasswdUser( $this->{session} );
# force-change them to users2 password again, Verify emails have survived.
foreach my $user ( sort keys %$users1 ) {
my $added = $impl->setPassword(
$user,
$users2->{$user}->{pass},
$users2->{$user}->{pass}
);
$this->assert_null( $impl->error() );
$this->assert_str_not_equals( $encrapted{$user},
$impl->fetchPass($user) );
$this->assert_null( $impl->error() );
$this->assert_str_equals( $users1->{$user}->{emails},
join( ";", $impl->getEmails($user) ) );
( $encrapted{$user}, $encoded{$user} ) = $impl->fetchPass($user);
$this->assert_str_equals( 'md5', $encoded{$user}->{enc} );
}
#dumpFile();
# Check and change passwords again, with a modified realm
# And use new value for Encoding
$Foswiki::cfg{Htpasswd}{Encoding} = 'htdigest-md5';
$Foswiki::cfg{AuthRealm} = 'Another New Realm';
$impl = new Foswiki::Users::HtPasswdUser( $this->{session} );
foreach my $user ( sort keys %$users1 ) {
my $added = $impl->setPassword(
$user,
$users2->{$user}->{pass},
$users2->{$user}->{pass}
);
$this->assert_null( $impl->error() );
$this->assert( $impl->checkPassword( $user, $users2->{$user}->{pass} ),
"For $user checkPassword" );
#$this->assert_null( $impl->error() );
$this->assert_str_not_equals( $encrapted{$user},
$impl->fetchPass($user) );
$this->assert_null( $impl->error() );
( $encrapted{$user}, $encoded{$user} ) = $impl->fetchPass($user);
}
#dumpFile();
$Foswiki::cfg{Htpasswd}{Encoding} = 'apache-md5';
$impl = new Foswiki::Users::HtPasswdUser( $this->{session} );
# force-change them to users2 password again, migrating to apache_md5.
foreach my $user ( sort keys %$users1 ) {
my $added = $impl->setPassword(
$user,
$users2->{$user}->{pass},
$users2->{$user}->{pass}
);
$this->assert_null( $impl->error() );
$this->assert_str_not_equals( $encrapted{$user},
$impl->fetchPass($user) );
$this->assert_null( $impl->error() );
$this->assert_str_equals( $users1->{$user}->{emails},
join( ";", $impl->getEmails($user) ) );
( $encrapted{$user}, $encoded{$user} ) = $impl->fetchPass($user);
$this->assert_str_equals( 'apache-md5', $encoded{$user}->{enc} );
}
#dumpFile();
}
sub dumpFile {
my $IN_FILE;
open( $IN_FILE, '<', "$Foswiki::cfg{TempfileDir}/junkpasswd" );
my $line;
while ( defined( $line = <$IN_FILE> ) ) {
print $line . "\n";
}
}
sub test_htpasswd_crypt_md5 {
my $this = shift;
@@ -196,21 +387,12 @@ sub test_htpasswd_crypt_crypt {
sub test_htpasswd_sha1 {
my $this = shift;
eval 'use MIME::Base64';
if ($@) {
my $mess = $@;
$mess =~ s/\(\@INC contains:.*$//s;
$this->expect_failure();
$this->annotate("CANNOT RUN SHA1 TESTS: $mess");
return;
}
eval 'use Digest::SHA';
if ($@) {
my $mess = $@;
$mess =~ s/\(\@INC contains:.*$//s;
$this->expect_failure();
$this->annotate("CANNOT RUN SHA1 TESTS: $mess");
return;
}
$Foswiki::cfg{Htpasswd}{Encoding} = 'sha1';
@@ -221,22 +403,41 @@ sub test_htpasswd_sha1 {
sub test_htpasswd_md5 {
my $this = shift;
eval 'use Digest::MD5';
$Foswiki::cfg{Htpasswd}{Encoding} = 'md5';
my $impl = new Foswiki::Users::HtPasswdUser( $this->{session} );
$this->assert($impl);
$this->doTests( $impl, 0 );
}
sub test_htpasswd_htdigest_md5 {
my $this = shift;
$Foswiki::cfg{Htpasswd}{Encoding} = 'htdigest-md5';
my $impl = new Foswiki::Users::HtPasswdUser( $this->{session} );
$this->assert($impl);
$this->doTests( $impl, 0 );
}
sub test_htpasswd_apache_md5 {
my $this = shift;
eval 'use Crypt::PasswdMD5';
if ($@) {
my $mess = $@;
$mess =~ s/\(\@INC contains:.*$//s;
$this->expect_failure();
$this->annotate("CANNOT RUN MD5 TESTS: $mess");
return;
$this->annotate("CANNOT RUN APACHE MD5 TESTS: $mess");
}
$Foswiki::cfg{Htpasswd}{Encoding} = 'md5';
$Foswiki::cfg{Htpasswd}{Encoding} = 'apache-md5';
my $impl = new Foswiki::Users::HtPasswdUser( $this->{session} );
$this->assert($impl);
$this->doTests( $impl, 0 );
}
sub test_htpasswd_apache {
sub test_ApacheHtpasswdUser {
my $this = shift;
eval "use Foswiki::Users::ApacheHtpasswdUser";
View
@@ -447,27 +447,51 @@ $Foswiki::cfg{MinPasswordLength} = 7;
# password file with the right encoding.
$Foswiki::cfg{Htpasswd}{FileName} = '$Foswiki::cfg{DataDir}/.htpasswd';
# **SELECT crypt,sha1,md5,plain,crypt-md5**
# Password encryption, for the Foswiki::Users::HtPasswdUser password manager.
# You can use the <tt>htpasswd</tt> Apache program to create a new
# password file with the right encoding. <b>Caution:</b> Changing the password
# encoding will invalidate all current passwords.
# **SELECT htdigest-md5,sha1,apache-md5,crypt-md5,crypt,plain**
# Password encryption, for the <tt>Foswiki::Users::HtPasswdUser</tt> password manager. This
# specifies the type of password hash to generate when writing entries to <tt>.htpasswd</tt>
# It is also used when reading password entries unless the parameter
# <tt>{Htpasswd}{AutoDetect}</tt> is enabled.
# <br /><br />
# The choices in order of strongest to lowest strength:
# <dl>
# <dt>crypt</dt><dd>is the default. <b>Caution:</b>
# crypt encoding only uses the first 8 characters of the password. Extra characters
# are silently discarded.</dd>
# <dt>sha1</dt><dd> is recommended for use on Windows.</dd>
# <dt>md5</dt><dd> htdigest format - useful on sites where password files are required
# to be portable. In this case, the {AuthRealm} is used with the username
# and password to generate the encrypted form of the password, thus:
# <tt>user:{AuthRealm}:password</tt>. Take note of this, because it means that
# if the {AuthRealm} changes, any existing MD5 encoded passwords will be
# invalidated by the change!</dd>
# <dt>plain</dt><dd> stores passwords as plain text (no encryption).</dd>
# <dt>crypt-md5</dt><dd>Enable use of standard libc (/etc/shadow) crypt-md5 password (like $1$saltsalt$hashashhashhashhash...$) which are stronger than the crypt paswords, salted, and the salt is stored in the encrypted password string as in normal crypt passwords. </dd>
# <dt>(HTTPS)</dt><dd> Any below encoding over an HTTPS SSL connection. (Not a selection here.)</dd>
# <dt><tt>htdigest-md5</tt></dt><dd> Strongest only when combined with the <tt>Foswiki::LoginManager::ApacheLogin</tt>
# Useful on sites where password files are required to be
# portable. The <tt>{AuthRealm}</tt> value is used with the username and password to generate
# the encrypted form of the password, thus: <tt>user:{AuthRealm}:hash</tt>.
# This encoding is generated by the Apache <tt>htdigest</tt> command.</dd>
# <dt><tt>sha1</tt></dt><dd> is recommended. It has the strongest hash. This is the encoding
# generated by the <tt>htpasswd -s</tt> command (<tt>userid:{SHA}hash</tt>).</dd>
# <dt><tt>apache-md5</tt></dt><dd> Enable an Apache-specific algorithm using an iterated
# (1,000 times) MD5 digest of various combinations of a random 32-bit salt and the password
# (<tt>userid:$apr1$salt$hash</tt>).
# This is the encoding generated by the <tt>htpasswd -m</tt> command.</dd>
# <dt><tt>crypt-md5</tt></dt><dd> Enable use of standard libc (/etc/shadow) crypt-md5 password
# (like <tt>user:$1$salt$hash:email</tt>). Unlike <tt>crypt</tt> encoding, it does not suffer from password truncation.
# Passwords are salted, and the salt is stored in the encrypted password string as in normal crypt passwords. This
# encoding is understood by Apache but cannot be generated by the <tt>htpasswd</tt> command.</dd>
# <dt><tt>crypt</tt></dt><dd> is the default. <b>Not Recommended.</b> crypt encoding only
# uses the first 8 characters of the password. Extra characters are silently discarded.
# This is the default generated by the Apache <tt>htpasswd</tt> command (<tt>user:hash:email</tt>)</dd>
# <dt><tt>plain</tt></dt><dd> stores passwords as plain text (no encryption). Useful for testing. Not compatible with <tt>{AutoDetect}</tt> option.</dd>
# </dl>
# If you need to create entries in <tt>.htpasswd</tt> before Foswiki is operational, you can use the
# <tt>htpasswd</tt> or <tt>htdigest</tt> Apache program to create a new password file with the correct
# encoding. Use caution however as these programs do not support the email addresses stored by Foswiki in
# the <tt>.htpasswd</tt> file.
$Foswiki::cfg{Htpasswd}{Encoding} = 'crypt';
# **BOOLEAN**
# Allow the <tt>Foswiki::Users::HtPasswdUser</tt>password check routines to auto-detect the stored encoding type. Enable
# this to allow migration from one encoding format to another format. Note that this does
# add a small overhead to the parsing of the <tt>.htpasswd</tt> file. Tests show approximately 1ms per 1000 entries. It should be used
# with caution unless you are using CGI acceleration such as FastCGI or mod_perl.
#
# This option is not compatible with <tt>plain</tt> text passwords.
$Foswiki::cfg{Htpasswd}{AutoDetect} = $FALSE;
#---++ Registration
# <p>Registration is the process by which new users register themselves with
# Foswiki.</p>
Oops, something went wrong.

0 comments on commit 54914b0

Please sign in to comment.