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

(-)a/C4/Auth.pm (-88 / +2 lines)
Lines 23-30 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
28
26
29
require Exporter;
27
require Exporter;
30
use C4::Context;
28
use C4::Context;
Lines 33-38 use C4::Branch; # GetBranches Link Here
33
use C4::VirtualShelves;
31
use C4::VirtualShelves;
34
use POSIX qw/strftime/;
32
use POSIX qw/strftime/;
35
use List::MoreUtils qw/ any /;
33
use List::MoreUtils qw/ any /;
34
use Koha::Passwords qw/ hash_password /;
36
35
37
# use utf8;
36
# use utf8;
38
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
37
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
Lines 50-56 BEGIN { Link Here
50
    @EXPORT      = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
49
    @EXPORT      = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
51
    @EXPORT_OK   = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
50
    @EXPORT_OK   = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
52
                      &get_all_subpermissions &get_user_subpermissions
51
                      &get_all_subpermissions &get_user_subpermissions
53
                      ParseSearchHistoryCookie hash_password
52
                      ParseSearchHistoryCookie
54
                   );
53
                   );
55
    %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
54
    %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
56
    $ldap        = C4::Context->config('useldapserver') || 0;
55
    $ldap        = C4::Context->config('useldapserver') || 0;
Lines 1488-1578 sub get_session { Link Here
1488
    return $session;
1487
    return $session;
1489
}
1488
}
1490
1489
1491
# Using Bcrypt method for hashing. This can be changed to something else in future, if needed.
1492
sub hash_password {
1493
    my $password = shift;
1494
1495
    # Generate a salt if one is not passed
1496
    my $settings = shift;
1497
    unless( defined $settings ){ # if there are no settings, we need to create a salt and append settings
1498
    # Set the cost to 8 and append a NULL
1499
        $settings = '$2a$08$'.en_base64(generate_salt('weak', 16));
1500
    }
1501
    # Encrypt it
1502
    return bcrypt($password, $settings);
1503
}
1504
1505
=head2 generate_salt
1506
1507
    use C4::Auth;
1508
    my $salt = C4::Auth::generate_salt($strength, $length);
1509
1510
=over
1511
1512
=item strength
1513
1514
For general password salting a C<$strength> of C<weak> is recommend,
1515
For generating a server-salt a C<$strength> of C<strong> is recommended
1516
1517
'strong' uses /dev/random which may block until sufficient entropy is acheived.
1518
'weak' uses /dev/urandom and is non-blocking.
1519
1520
=item length
1521
1522
C<$length> is a positive integer which specifies the desired length of the returned string
1523
1524
=back
1525
1526
=cut
1527
1528
1529
# the implementation of generate_salt is loosely based on Crypt::Random::Provider::File
1530
sub generate_salt {
1531
    # strength is 'strong' or 'weak'
1532
    # length is number of bytes to read, positive integer
1533
    my ($strength, $length) = @_;
1534
1535
    my $source;
1536
1537
    if( $length < 1 ){
1538
        die "non-positive strength of '$strength' passed to C4::Auth::generate_salt\n";
1539
    }
1540
1541
    if( $strength eq "strong" ){
1542
        $source = '/dev/random'; # blocking
1543
    } else {
1544
        unless( $strength eq 'weak' ){
1545
            warn "unsuppored strength of '$strength' passed to C4::Auth::generate_salt, defaulting to 'weak'\n";
1546
        }
1547
        $source = '/dev/urandom'; # non-blocking
1548
    }
1549
1550
    sysopen SOURCE, $source, O_RDONLY
1551
        or die "failed to open source '$source' in C4::Auth::generate_salt\n";
1552
1553
    # $bytes is the bytes just read
1554
    # $string is the concatenation of all the bytes read so far
1555
    my( $bytes, $string ) = ("", "");
1556
1557
    # keep reading until we have $length bytes in $strength
1558
    while( length($string) < $length ){
1559
        # return the number of bytes read, 0 (EOF), or -1 (ERROR)
1560
        my $return = sysread SOURCE, $bytes, $length - length($string);
1561
1562
        # if no bytes were read, keep reading (if using /dev/random it is possible there was insufficient entropy so this may block)
1563
        next unless $return;
1564
        if( $return == -1 ){
1565
            die "error while reading from $source in C4::Auth::generate_salt\n";
1566
        }
1567
1568
        $string .= $bytes;
1569
    }
1570
1571
    close SOURCE;
1572
    return $string;
1573
}
1574
1575
1576
sub checkpw {
1490
sub checkpw {
1577
    my ( $dbh, $userid, $password, $query ) = @_;
1491
    my ( $dbh, $userid, $password, $query ) = @_;
1578
1492
(-)a/C4/Auth_with_ldap.pm (-1 / +2 lines)
Lines 26-32 use C4::Context; Link Here
26
use C4::Members qw(AddMember changepassword);
26
use C4::Members qw(AddMember changepassword);
27
use C4::Members::Attributes;
27
use C4::Members::Attributes;
28
use C4::Members::AttributeTypes;
28
use C4::Members::AttributeTypes;
29
use C4::Auth qw(hash_password checkpw_internal);
29
use C4::Auth qw(checkpw_internal);
30
use Koha::Passwords qw( hash_password );
30
use List::MoreUtils qw( any );
31
use List::MoreUtils qw( any );
31
use Net::LDAP;
32
use Net::LDAP;
32
use Net::LDAP::Filter;
33
use Net::LDAP::Filter;
(-)a/C4/Members.pm (-1 / +1 lines)
Lines 38-45 use C4::NewsChannels; #get slip news Link Here
38
use DateTime;
38
use DateTime;
39
use DateTime::Format::DateParse;
39
use DateTime::Format::DateParse;
40
use Koha::DateUtils;
40
use Koha::DateUtils;
41
use Koha::Passwords qw( hash_password );
41
use Text::Unaccent qw( unac_string );
42
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
(-)a/Koha/Passwords.pm (+162 lines)
Line 0 Link Here
1
package Koha::Passwords;
2
3
# Copyright 2013 Catalyst IT Ltd.
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
=head1 NAME
21
22
Koha::Passwords - A central point to handle user passwords
23
24
=head1 SYNOPSIS
25
26
    use Koha::Passwords;
27
    my $hashed1 = Koha::Passwords::hash_password($password1);
28
    my $hashed2 = Koha::Passwords::hash_password($password2, $hashed1);
29
    print '$password1 is the same as $password2' if $hashed1 eq $hashed2;
30
31
=head1 DESCRIPTION
32
33
This provides the hashing function that should be used whenever you want to
34
do something with a password.
35
36
=head1 FUNCTIONS
37
38
=cut
39
40
use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt
41
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
42
use Modern::Perl;
43
44
require Exporter;
45
46
use vars qw(@ISA @EXPORT @EXPORT_OK);
47
48
BEGIN {
49
    @ISA       = qw(Exporter);
50
    @EXPORT    = qw();
51
    @EXPORT_OK = qw(hash_password);
52
}
53
54
=head2 hash_password
55
56
    $hashed = hash_password($password, [$settings]);
57
58
This hashes a password using whatever the secure algorithm of the day is.
59
The optional C<$settings> parameter describes what an existing password is
60
hashed with, and is attached to the start of the returned string. This means
61
that you can test a password by passing in the password you were given,
62
the hash you already have, and compare the hash you get out to the one you
63
have.
64
65
This function is exportable.
66
67
=cut
68
69
sub hash_password {
70
    my $password = shift;
71
    # In case it gets called with -> instead of ::
72
    $password = shift if $password eq 'Koha::Passwords';
73
74
    # Generate a salt if one is not passed
75
    my $settings = shift;
76
    unless ( defined $settings )
77
    {   # if there are no settings, we need to create a salt and append settings
78
            # Set the cost to 8 and append a NULL
79
        $settings = '$2a$08$' . en_base64( generate_salt( 'weak', 16 ) );
80
    }
81
82
    # Encrypt it
83
    return bcrypt( $password, $settings );
84
}
85
86
=head2 generate_salt
87
88
    my $salt = generate_salt($strength, $length);
89
90
=over
91
92
=item strength
93
94
For general password salting a C<$strength> of C<weak> is recommend,
95
For generating a server-salt a C<$strength> of C<strong> is recommended
96
97
'strong' uses /dev/random which may block until sufficient entropy is acheived.
98
'weak' uses /dev/urandom and is non-blocking.
99
100
=item length
101
102
C<$length> is a positive integer which specifies the desired length of the returned string
103
104
=back
105
106
This function is not exportable.
107
108
=cut
109
110
# the implementation of generate_salt is loosely based on Crypt::Random::Provider::File
111
sub generate_salt {
112
113
    # strength is 'strong' or 'weak'
114
    # length is number of bytes to read, positive integer
115
    my ( $strength, $length ) = @_;
116
117
    my $source;
118
119
    if ( $length < 1 ) {
120
        die
121
"non-positive strength of '$strength' passed to Koha::Passwords::generate_salt\n";
122
    }
123
124
    if ( $strength eq "strong" ) {
125
        $source = '/dev/random';    # blocking
126
    }
127
    else {
128
        unless ( $strength eq 'weak' ) {
129
            warn
130
"unsuppored strength of '$strength' passed to Koha::Passwords::generate_salt, defaulting to 'weak'\n";
131
        }
132
        $source = '/dev/urandom';    # non-blocking
133
    }
134
135
    sysopen SOURCE, $source, O_RDONLY
136
      or die
137
      "failed to open source '$source' in Koha:Passwords::generate_salt\n";
138
139
    # $bytes is the bytes just read
140
    # $string is the concatenation of all the bytes read so far
141
    my ( $bytes, $string ) = ( "", "" );
142
143
    # keep reading until we have $length bytes in $strength
144
    while ( length($string) < $length ) {
145
146
        # return the number of bytes read, 0 (EOF), or -1 (ERROR)
147
        my $return = sysread SOURCE, $bytes, $length - length($string);
148
149
# if no bytes were read, keep reading (if using /dev/random it is possible there was insufficient entropy so this may block)
150
        next unless $return;
151
        if ( $return == -1 ) {
152
            die
153
"error while reading from $source in Koha::Passwords::generate_salt\n";
154
        }
155
156
        $string .= $bytes;
157
    }
158
159
    close SOURCE;
160
    return $string;
161
}
162
(-)a/members/member-password.pl (-2 / +2 lines)
Lines 7-13 Link Here
7
use strict;
7
use strict;
8
use warnings;
8
use warnings;
9
9
10
use C4::Auth;
11
use C4::Output;
10
use C4::Output;
12
use C4::Context;
11
use C4::Context;
13
use C4::Members;
12
use C4::Members;
Lines 15-20 use C4::Branch; Link Here
15
use C4::Circulation;
14
use C4::Circulation;
16
use CGI;
15
use CGI;
17
use C4::Members::Attributes qw(GetBorrowerAttributes);
16
use C4::Members::Attributes qw(GetBorrowerAttributes);
17
use Koha::Passwords;
18
18
19
use Digest::MD5 qw(md5_base64);
19
use Digest::MD5 qw(md5_base64);
20
20
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=C4::Auth::hash_password($input->param('newpassword'));
58
    my $digest=Koha::Passwords::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 (-1 / +1 lines)
Lines 29-35 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
use Koha::Passwords qw( hash_password );
33
33
34
my $query = new CGI;
34
my $query = new CGI;
35
my $dbh   = C4::Context->dbh;
35
my $dbh   = C4::Context->dbh;
(-)a/t/Koha_Passwords.t (-1 / +34 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright 2013 Catalyst IT Ltd.
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use strict;
21
use warnings;
22
23
use Test::More tests => 4;
24
25
BEGIN {
26
    use_ok('Koha::Passwords');
27
}
28
29
my $hashed1 = Koha::Passwords::hash_password('sup3rs3kr1t');
30
ok(defined($hashed1), 'Hash 1 created');
31
my $hashed2 = Koha::Passwords::hash_password('sup3rs3kr1t', $hashed1);
32
ok(defined($hashed1), 'Hash 2 created');
33
34
cmp_ok($hashed1, 'eq', $hashed2, 'Hashes match');

Return to bug 11013