View | Details | Raw Unified | Return to bug 9611
Collapse All | Expand All

(-)a/C4/Auth.pm (-5 / +109 lines)
Lines 23-28 use Digest::MD5 qw(md5_base64); Link Here
23
use JSON qw/encode_json decode_json/;
23
use JSON qw/encode_json decode_json/;
24
use URI::Escape;
24
use URI::Escape;
25
use CGI::Session;
25
use CGI::Session;
26
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
27
use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt
26
28
27
require Exporter;
29
require Exporter;
28
use C4::Context;
30
use C4::Context;
Lines 47-53 BEGIN { Link Here
47
    @ISA         = qw(Exporter);
49
    @ISA         = qw(Exporter);
48
    @EXPORT      = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
50
    @EXPORT      = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
49
    @EXPORT_OK   = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions
51
    @EXPORT_OK   = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions
50
                      ParseSearchHistoryCookie
52
                      ParseSearchHistoryCookie hash_password
51
                   );
53
                   );
52
    %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
54
    %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
53
    $ldap        = C4::Context->config('useldapserver') || 0;
55
    $ldap        = C4::Context->config('useldapserver') || 0;
Lines 1464-1469 sub get_session { Link Here
1464
    return $session;
1466
    return $session;
1465
}
1467
}
1466
1468
1469
# Using Bcrypt method for hashing. This can be changed to something else in future, if needed.
1470
sub hash_password {
1471
    my $password = shift;
1472
1473
    # Generate a salt if one is not passed
1474
    my $settings = shift;
1475
    unless( defined $settings ){ # if there are no settings, we need to create a salt and append settings
1476
    # Set the cost to 8 and append a NULL
1477
        $settings = '$2a$08$'.en_base64(generate_salt('weak', 16));
1478
    }
1479
    # Encrypt it
1480
    return bcrypt($password, $settings);
1481
}
1482
1483
=head2 generate_salt
1484
1485
    use C4::Auth;
1486
    my $salt = C4::Auth::generate_salt($strength, $length);
1487
1488
=item strength
1489
1490
For general password salting a C<$strength> of C<weak> is recommend,
1491
For generating a server-salt a C<$strength> of C<strong> is recommended
1492
1493
'strong' uses /dev/random which may block until sufficient entropy is acheived.
1494
'weak' uses /dev/urandom and is non-blocking.
1495
1496
=back
1497
1498
=item length
1499
1500
C<$length> is a positive integer which specifies the desired length of the returned string
1501
1502
=back
1503
1504
=cut
1505
1506
1507
# the implementation of generate_salt is loosely based on Crypt::Random::Provider::File
1508
sub generate_salt {
1509
    # strength is 'strong' or 'weak'
1510
    # length is number of bytes to read, positive integer
1511
    my ($strength, $length) = @_;
1512
1513
    my $source;
1514
1515
    if( $length < 1 ){
1516
        die "non-positive strength of '$strength' passed to C4::Auth::generate_salt\n";
1517
    }
1518
1519
    if( $strength eq "strong" ){
1520
        $source = '/dev/random'; # blocking
1521
    } else {
1522
        unless( $strength eq 'weak' ){
1523
            warn "unsuppored strength of '$strength' passed to C4::Auth::generate_salt, defaulting to 'weak'\n";
1524
        }
1525
        $source = '/dev/urandom'; # non-blocking
1526
    }
1527
1528
    sysopen SOURCE, $source, O_RDONLY
1529
        or die "failed to open source '$source' in C4::Auth::generate_salt\n";
1530
1531
    # $bytes is the bytes just read
1532
    # $string is the concatenation of all the bytes read so far
1533
    my( $bytes, $string ) = ("", "");
1534
1535
    # keep reading until we have $length bytes in $strength
1536
    while( length($string) < $length ){
1537
        # return the number of bytes read, 0 (EOF), or -1 (ERROR)
1538
        my $return = sysread SOURCE, $bytes, $length - length($string);
1539
1540
        # if no bytes were read, keep reading (if using /dev/random it is possible there was insufficient entropy so this may block)
1541
        next unless $return;
1542
        if( $return == -1 ){
1543
            die "error while reading from $source in C4::Auth::generate_salt\n";
1544
        }
1545
1546
        $string .= $bytes;
1547
    }
1548
1549
    close SOURCE;
1550
    return $string;
1551
}
1552
1553
1467
sub checkpw {
1554
sub checkpw {
1468
1555
1469
    my ( $dbh, $userid, $password, $query ) = @_;
1556
    my ( $dbh, $userid, $password, $query ) = @_;
Lines 1489-1498 sub checkpw { Link Here
1489
      );
1576
      );
1490
    $sth->execute($userid);
1577
    $sth->execute($userid);
1491
    if ( $sth->rows ) {
1578
    if ( $sth->rows ) {
1492
        my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1579
        my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1493
            $surname, $branchcode, $flags )
1580
            $surname, $branchcode, $flags )
1494
          = $sth->fetchrow;
1581
          = $sth->fetchrow;
1495
        if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1582
1583
        # check what encryption algorithm was implemented: Bcrypt - if the hash starts with '$2' it is Bcrypt else md5
1584
        my $hash;
1585
        if ( substr($stored_hash,0,2) eq '$2') {
1586
            $hash = hash_password($password, $stored_hash);
1587
        } else {
1588
            $hash = md5_base64($password);
1589
        }
1590
        if ( $hash eq $stored_hash and $stored_hash ne "!") {
1496
1591
1497
            C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1592
            C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1498
                $firstname, $surname, $branchcode, $flags );
1593
                $firstname, $surname, $branchcode, $flags );
Lines 1505-1514 sub checkpw { Link Here
1505
      );
1600
      );
1506
    $sth->execute($userid);
1601
    $sth->execute($userid);
1507
    if ( $sth->rows ) {
1602
    if ( $sth->rows ) {
1508
        my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1603
        my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1509
            $surname, $branchcode, $flags )
1604
            $surname, $branchcode, $flags )
1510
          = $sth->fetchrow;
1605
          = $sth->fetchrow;
1511
        if ( md5_base64($password) eq $md5password ) {
1606
1607
        # check what encryption algorithm was implemented: Bcrypt - if the hash starts with '$2' it is Bcrypt else md5
1608
        my $hash;
1609
        if ( substr($stored_hash,0,2) eq '$2') {
1610
            $hash = hash_password($password, $stored_hash);
1611
        } else {
1612
            $hash = md5_base64($password);
1613
        }
1614
1615
        if ( $hash eq $stored_hash ) {
1512
1616
1513
            C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1617
            C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1514
                $firstname, $surname, $branchcode, $flags );
1618
                $firstname, $surname, $branchcode, $flags );
(-)a/C4/Members.pm (-29 / +6 lines)
Lines 24-30 use strict; Link Here
24
#use warnings; FIXME - Bug 2505
24
#use warnings; FIXME - Bug 2505
25
use C4::Context;
25
use C4::Context;
26
use C4::Dates qw(format_date_in_iso format_date);
26
use C4::Dates qw(format_date_in_iso format_date);
27
use Digest::MD5 qw(md5_base64);
28
use String::Random qw( random_string );
27
use String::Random qw( random_string );
29
use Date::Calc qw/Today Add_Delta_YM check_date Date_to_Days/;
28
use Date::Calc qw/Today Add_Delta_YM check_date Date_to_Days/;
30
use C4::Log; # logaction
29
use C4::Log; # logaction
Lines 40-45 use DateTime; Link Here
40
use DateTime::Format::DateParse;
39
use DateTime::Format::DateParse;
41
use Koha::DateUtils;
40
use Koha::DateUtils;
42
use Text::Unaccent qw( unac_string );
41
use Text::Unaccent qw( unac_string );
42
use C4::Auth qw(hash_password);
43
43
44
our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
44
our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
45
45
Lines 250-256 sub Search { Link Here
250
                $filter = [ $filter ];
250
                $filter = [ $filter ];
251
                push @$filter, {"borrowernumber"=>$matching_records};
251
                push @$filter, {"borrowernumber"=>$matching_records};
252
            }
252
            }
253
		}
253
        }
254
    }
254
    }
255
255
256
    # $showallbranches was not used at the time SearchMember() was mainstreamed into Search().
256
    # $showallbranches was not used at the time SearchMember() was mainstreamed into Search().
Lines 283-289 sub Search { Link Here
283
    }
283
    }
284
    $searchtype ||= "start_with";
284
    $searchtype ||= "start_with";
285
285
286
	return SearchInTable( "borrowers", $filter, $orderby, $limit, $columns_out, $search_on_fields, $searchtype );
286
    return SearchInTable( "borrowers", $filter, $orderby, $limit, $columns_out, $search_on_fields, $searchtype );
287
}
287
}
288
288
289
=head2 GetMemberDetails
289
=head2 GetMemberDetails
Lines 750-760 sub ModMember { Link Here
750
        if ($data{password} eq '****' or $data{password} eq '') {
750
        if ($data{password} eq '****' or $data{password} eq '') {
751
            delete $data{password};
751
            delete $data{password};
752
        } else {
752
        } else {
753
            $data{password} = md5_base64($data{password});
753
            $data{password} = hash_password($data{password});
754
        }
754
        }
755
    }
755
    }
756
    my $old_categorycode = GetBorrowerCategorycode( $data{borrowernumber} );
756
    my $old_categorycode = GetBorrowerCategorycode( $data{borrowernumber} );
757
	my $execute_success=UpdateInTable("borrowers",\%data);
757
    my $execute_success=UpdateInTable("borrowers",\%data);
758
    if ($execute_success) { # only proceed if the update was a success
758
    if ($execute_success) { # only proceed if the update was a success
759
        # ok if its an adult (type) it may have borrowers that depend on it as a guarantor
759
        # ok if its an adult (type) it may have borrowers that depend on it as a guarantor
760
        # so when we update information for an adult we should check for guarantees and update the relevant part
760
        # so when we update information for an adult we should check for guarantees and update the relevant part
Lines 805-814 sub AddMember { Link Here
805
    }
805
    }
806
806
807
    # create a disabled account if no password provided
807
    # create a disabled account if no password provided
808
    $data{'password'} = ($data{'password'})? md5_base64($data{'password'}) : '!';
808
    $data{'password'} = ($data{'password'})? hash_password($data{'password'}) : '!';
809
    $data{'borrowernumber'}=InsertInTable("borrowers",\%data);
809
    $data{'borrowernumber'}=InsertInTable("borrowers",\%data);
810
810
811
812
    # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
811
    # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
813
    logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
812
    logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
814
813
Lines 1466-1493 sub GetExpiryDate { Link Here
1466
    }
1465
    }
1467
}
1466
}
1468
1467
1469
=head2 checkuserpassword (OUEST-PROVENCE)
1470
1471
check for the password and login are not used
1472
return the number of record 
1473
0=> NOT USED 1=> USED
1474
1475
=cut
1476
1477
sub checkuserpassword {
1478
    my ( $borrowernumber, $userid, $password ) = @_;
1479
    $password = md5_base64($password);
1480
    my $dbh = C4::Context->dbh;
1481
    my $sth =
1482
      $dbh->prepare(
1483
"Select count(*) from borrowers where borrowernumber !=? and userid =? and password=? "
1484
      );
1485
    $sth->execute( $borrowernumber, $userid, $password );
1486
    my $number_rows = $sth->fetchrow;
1487
    return $number_rows;
1488
1489
}
1490
1491
=head2 GetborCatFromCatType
1468
=head2 GetborCatFromCatType
1492
1469
1493
  ($codes_arrayref, $labels_hashref) = &GetborCatFromCatType();
1470
  ($codes_arrayref, $labels_hashref) = &GetborCatFromCatType();
(-)a/members/member-password.pl (-1 / +1 lines)
Lines 55-61 my $minpw = C4::Context->preference('minPasswordLength'); Link Here
55
push(@errors,'SHORTPASSWORD') if( $newpassword && $minpw && (length($newpassword) < $minpw ) );
55
push(@errors,'SHORTPASSWORD') if( $newpassword && $minpw && (length($newpassword) < $minpw ) );
56
56
57
if ( $newpassword  && !scalar(@errors) ) {
57
if ( $newpassword  && !scalar(@errors) ) {
58
    my $digest=md5_base64($input->param('newpassword'));
58
    my $digest=C4::Auth::hash_password($input->param('newpassword'));
59
    my $uid = $input->param('newuserid');
59
    my $uid = $input->param('newuserid');
60
    my $dbh=C4::Context->dbh;
60
    my $dbh=C4::Context->dbh;
61
    if (changepassword($uid,$member,$digest)) {
61
    if (changepassword($uid,$member,$digest)) {
(-)a/opac/opac-passwd.pl (-4 / +10 lines)
Lines 29-34 use Digest::MD5 qw(md5_base64); Link Here
29
use C4::Circulation;
29
use C4::Circulation;
30
use C4::Members;
30
use C4::Members;
31
use C4::Output;
31
use C4::Output;
32
use C4::Auth qw(hash_password);
32
33
33
my $query = new CGI;
34
my $query = new CGI;
34
my $dbh   = C4::Context->dbh;
35
my $dbh   = C4::Context->dbh;
Lines 57-63 if ( C4::Context->preference("OpacPasswordChange") ) { Link Here
57
            if ( $query->param('Newkey') eq $query->param('Confirm')
58
            if ( $query->param('Newkey') eq $query->param('Confirm')
58
                && length( $query->param('Confirm') ) >= $minpasslen )
59
                && length( $query->param('Confirm') ) >= $minpasslen )
59
            {    # Record password
60
            {    # Record password
60
                my $clave = md5_base64( $query->param('Newkey') );
61
                my $clave = hash_password( $query->param('Newkey') );
61
                $sth->execute( $clave, $borrowernumber );
62
                $sth->execute( $clave, $borrowernumber );
62
                $template->param( 'password_updated' => '1' );
63
                $template->param( 'password_updated' => '1' );
63
                $template->param( 'borrowernumber'   => $borrowernumber );
64
                $template->param( 'borrowernumber'   => $borrowernumber );
Lines 113-120 sub goodkey { Link Here
113
      $dbh->prepare("SELECT password FROM borrowers WHERE borrowernumber=?");
114
      $dbh->prepare("SELECT password FROM borrowers WHERE borrowernumber=?");
114
    $sth->execute($borrowernumber);
115
    $sth->execute($borrowernumber);
115
    if ( $sth->rows ) {
116
    if ( $sth->rows ) {
116
        my ($md5password) = $sth->fetchrow;
117
        my $hash;
117
        if ( md5_base64($key) eq $md5password ) { return 1; }
118
        my ($stored_hash) = $sth->fetchrow;
119
        if ( substr($stored_hash,0,2) eq '$2') {
120
            $hash = hash_password($key, $stored_hash);
121
        } else {
122
            $hash = md5_base64($key);
123
        }
124
        if ( $hash eq $stored_hash ) { return 1; }
118
        else { return 0; }
125
        else { return 0; }
119
    }
126
    }
120
    else { return 0; }
127
    else { return 0; }
121
- 

Return to bug 9611