1 package Koha::AuthUtils;
3 # Copyright 2013 Catalyst IT
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
21 use Crypt::Eksblowfish::Bcrypt qw( bcrypt en_base64 );
23 use Fcntl qw( O_RDONLY ); # O_RDONLY is used in generate_salt
24 use List::MoreUtils qw( any );
25 use String::Random qw( random_string );
26 use Koha::Exceptions::Password;
31 our (@ISA, @EXPORT_OK);
35 @EXPORT_OK = qw(hash_password get_script_name is_password_valid);
39 Koha::AuthUtils - utility routines for authentication
43 use Koha::AuthUtils qw/hash_password/;
44 my $hash = hash_password($password);
48 This module provides utility functions related to managing
55 my $hash = Koha::AuthUtils::hash_password($password, $settings);
57 Hash I<$password> using Bcrypt. Accepts an extra I<$settings> parameter for salt.
58 If I<$settings> is not passed, a new salt is generated.
60 WARNING: If this method implementation is changed in the future, as of
61 bug 28772 there's at least one DBRev that uses this code and should
68 $password = Encode::encode( 'UTF-8', $password )
69 if Encode::is_utf8($password);
71 # Generate a salt if one is not passed
73 unless( defined $settings ){ # if there are no settings, we need to create a salt and append settings
74 # Set the cost to 8 and append a NULL
75 $settings = '$2a$08$'.en_base64(generate_salt('weak', 16));
78 return bcrypt($password, $settings);
83 my $salt = Koha::Auth::generate_salt($strength, $length);
89 For general password salting a C<$strength> of C<weak> is recommend,
90 For generating a server-salt a C<$strength> of C<strong> is recommended
92 'strong' uses /dev/random which may block until sufficient entropy is achieved.
93 'weak' uses /dev/urandom and is non-blocking.
97 C<$length> is a positive integer which specifies the desired length of the returned string
104 # the implementation of generate_salt is loosely based on Crypt::Random::Provider::File
106 # strength is 'strong' or 'weak'
107 # length is number of bytes to read, positive integer
108 my ($strength, $length) = @_;
113 die "non-positive strength of '$strength' passed to Koha::AuthUtils::generate_salt\n";
116 if( $strength eq "strong" ){
117 $source = '/dev/random'; # blocking
119 unless( $strength eq 'weak' ){
120 warn "unsuppored strength of '$strength' passed to Koha::AuthUtils::generate_salt, defaulting to 'weak'\n";
122 $source = '/dev/urandom'; # non-blocking
126 sysopen $source_fh, $source, O_RDONLY
127 or die "failed to open source '$source' in Koha::AuthUtils::generate_salt\n";
129 # $bytes is the bytes just read
130 # $string is the concatenation of all the bytes read so far
131 my( $bytes, $string ) = ("", "");
133 # keep reading until we have $length bytes in $strength
134 while( length($string) < $length ){
135 # return the number of bytes read, 0 (EOF), or -1 (ERROR)
136 my $return = sysread $source_fh, $bytes, $length - length($string);
138 # if no bytes were read, keep reading (if using /dev/random it is possible there was insufficient entropy so this may block)
141 die "error while reading from $source in Koha::AuthUtils::generate_salt\n";
151 =head2 is_password_valid
153 my ( $is_valid, $error ) = is_password_valid( $password, $category );
155 return $is_valid == 1 if the password match category's minimum password length and strength if provided, or general minPasswordLength and RequireStrongPassword conditions
156 otherwise return $is_valid == 0 and $error will contain the error ('too_short' or 'too_weak')
160 sub is_password_valid {
161 my ($password, $category) = @_;
163 Koha::Exceptions::Password::NoCategoryProvided->throw();
165 my $minPasswordLength = $category->effective_min_password_length;
166 $minPasswordLength = 3 if not $minPasswordLength or $minPasswordLength < 3;
167 if ( length($password) < $minPasswordLength ) {
168 return ( 0, 'too_short' );
170 elsif ( $category->effective_require_strong_password ) {
171 return ( 0, 'too_weak' )
172 if $password !~ m|(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{$minPasswordLength,}|;
174 return ( 0, 'has_whitespaces' ) if $password =~ m[^\s|\s$];
178 =head2 generate_password
180 my password = generate_password($category);
182 Generate a password according to category's minimum password length and strength if provided, or to the minPasswordLength and RequireStrongPassword system preferences.
186 sub generate_password {
189 Koha::Exceptions::Password::NoCategoryProvided->throw();
191 my $minPasswordLength = $category->effective_min_password_length;
192 $minPasswordLength = 8 if not $minPasswordLength or $minPasswordLength < 8;
194 my ( $password, $is_valid );
196 $password = random_string('.' x $minPasswordLength );
197 ( $is_valid, undef ) = is_password_valid( $password, $category );
198 } while not $is_valid;
203 =head2 get_script_name
205 This returns the correct script name, for use in redirecting back to the correct page after showing
206 the login screen. It depends on details of the package Plack configuration, and should not be used
207 outside this context.
211 sub get_script_name {
212 if ( ( C4::Context->psgi_env ) && $ENV{SCRIPT_NAME} && $ENV{SCRIPT_NAME} =~ m,^/(intranet|opac)(.*), ) {
213 return '/cgi-bin/koha' . $2;
215 return $ENV{SCRIPT_NAME};
225 Crypt::Eksblowfish::Bcrypt(3)