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

(-)a/C4/Auth.pm (-39 / +140 lines)
Lines 19-29 package C4::Auth; Link Here
19
19
20
use strict;
20
use strict;
21
use warnings;
21
use warnings;
22
use Digest::MD5 qw(md5_base64);
22
use Digest::MD5 qw(md5_base64); #@DEPRECATED Digest::MD5, don't use it or you will get hurt.
23
use JSON qw/encode_json/;
23
use JSON qw/encode_json/;
24
use URI::Escape;
24
use URI::Escape;
25
use CGI::Session;
25
use CGI::Session;
26
use Scalar::Util qw(blessed);
26
use Scalar::Util qw(blessed);
27
use Try::Tiny;
27
28
28
require Exporter;
29
require Exporter;
29
use C4::Context;
30
use C4::Context;
Lines 34-39 use C4::Search::History; Link Here
34
use Koha;
35
use Koha;
35
use Koha::Borrowers;
36
use Koha::Borrowers;
36
use Koha::AuthUtils qw(hash_password);
37
use Koha::AuthUtils qw(hash_password);
38
use Koha::Auth;
39
use Koha::Auth::Component;
37
use POSIX qw/strftime/;
40
use POSIX qw/strftime/;
38
use List::MoreUtils qw/ any /;
41
use List::MoreUtils qw/ any /;
39
use Encode qw( encode is_utf8);
42
use Encode qw( encode is_utf8);
Lines 639-688 has authenticated. Link Here
639
642
640
=cut
643
=cut
641
644
645
=head _version_check
646
#@DEPRECATED See Bug 7174
647
use Koha::Auth::Component::* instead
648
=cut
649
642
sub _version_check {
650
sub _version_check {
643
    my $type  = shift;
651
    my $type  = shift;
644
    my $query = shift;
652
    my $query = shift;
645
    my $version;
653
    my $version;
646
654
647
    # If version syspref is unavailable, it means Koha is being installed,
655
    try {
648
    # and so we must redirect to OPAC maintenance page or to the WebInstaller
656
        Koha::Auth::Component::checkOPACMaintenance() if ( $type eq 'opac' );
649
    # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
657
        Koha::Auth::Component::checkVersion();
650
    if ( C4::Context->preference('OpacMaintenance') && $type eq 'opac' ) {
658
    } catch {
651
        warn "OPAC Install required, redirecting to maintenance";
659
        if (blessed($_)) {
652
        print $query->redirect("/cgi-bin/koha/maintenance.pl");
660
            if ($_->isa('Koha::Exception::VersionMismatch')) {
653
        safe_exit;
661
                # check that database and koha version are the same
654
    }
662
                # there is no DB version, it's a fresh install,
655
    unless ( $version = C4::Context->preference('Version') ) {    # assignment, not comparison
663
                # go to web installer
656
        if ( $type ne 'opac' ) {
664
                # there is a DB version, compare it to the code version
657
            warn "Install required, redirecting to Installer";
665
                my $warning = $_->error()." Redirecting to %s.";
658
            print $query->redirect("/cgi-bin/koha/installer/install.pl");
666
                if ( $type ne 'opac' ) {
659
        } else {
667
                    warn sprintf( $warning, 'Installer' );
660
            warn "OPAC Install required, redirecting to maintenance";
668
                    print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure");
661
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
669
                } else {
670
                    warn sprintf( "OPAC: " . $warning, 'maintenance' );
671
                    print $query->redirect("/cgi-bin/koha/maintenance.pl");
672
                }
673
                safe_exit;
674
            }
675
            elsif ($_->isa('Koha::Exception::ServiceTemporarilyUnavailable')) {
676
                # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
677
                warn "OPAC Install required, redirecting to maintenance";
678
                print $query->redirect("/cgi-bin/koha/maintenance.pl");
679
                safe_exit;
680
            }
681
            elsif ($_->isa('Koha::Exception::BadSystemPreference')) {
682
                # If version syspref is unavailable, it means Koha is being installed,
683
                # and so we must redirect to OPAC maintenance page or to the WebInstaller
684
                if ( $type ne 'opac' ) {
685
                    warn "Install required, redirecting to Installer";
686
                    print $query->redirect("/cgi-bin/koha/installer/install.pl");
687
                } else {
688
                    warn "OPAC Install required, redirecting to maintenance";
689
                    print $query->redirect("/cgi-bin/koha/maintenance.pl");
690
                }
691
                safe_exit;
692
            }
693
            else {
694
                warn "Unknown exception class ".ref($_)."\n";
695
                die $_; #Unhandled exception case
696
            }
662
        }
697
        }
663
        safe_exit;
698
        else {
664
    }
699
            die $_; #Not a Koha::Exception-object, so rethrow it
665
666
    # check that database and koha version are the same
667
    # there is no DB version, it's a fresh install,
668
    # go to web installer
669
    # there is a DB version, compare it to the code version
670
    my $kohaversion = Koha::version();
671
672
    # remove the 3 last . to have a Perl number
673
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
674
    $debug and print STDERR "kohaversion : $kohaversion\n";
675
    if ( $version < $kohaversion ) {
676
        my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
677
        if ( $type ne 'opac' ) {
678
            warn sprintf( $warning, 'Installer' );
679
            print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure");
680
        } else {
681
            warn sprintf( "OPAC: " . $warning, 'maintenance' );
682
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
683
        }
700
        }
684
        safe_exit;
701
    };
685
    }
686
}
702
}
687
703
688
sub _session_log {
704
sub _session_log {
Lines 702-708 sub _timeout_syspref { Link Here
702
    return $timeout;
718
    return $timeout;
703
}
719
}
704
720
721
=head checkauth
722
@DEPRECATED See Bug 7174
723
724
Compatibility layer for old Koha authentication system.
725
Tries to authenticate using Koha::Auth, but if no authentication mechanism is
726
identified, falls back to the deprecated legacy behaviour.
727
=cut
728
705
sub checkauth {
729
sub checkauth {
730
    my @params = @_; #Clone params so we don't accidentally change them if we fallback to checkauth_legacy()
731
    my $query = shift;
732
    my $authnotrequired = shift;
733
    my $flagsrequired   = shift;
734
    my $type            = shift;
735
    my $persona         = shift;
736
737
    my $borrower;
738
    try {
739
        if (not($authnotrequired) && not($persona)) {
740
            $borrower = Koha::Auth::authenticate($query, $flagsrequired, {authnotrequired => $authnotrequired});
741
        }
742
    } catch {
743
        if (blessed($_)) {
744
            if    ($_->isa('Koha::Exception::VersionMismatch')) {
745
746
                my $warning = $_->error()." Redirecting to %s.";
747
                if ( $type ne 'opac' ) {
748
                    warn sprintf( $warning, 'Installer' );
749
                    print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure");
750
                } else {
751
                    warn sprintf( "OPAC: " . $warning, 'maintenance' );
752
                    print $query->redirect("/cgi-bin/koha/maintenance.pl");
753
                }
754
                safe_exit;
755
756
            }
757
            elsif ($_->isa('Koha::Exception::BadSystemPreference')) {
758
759
                if ( $type ne 'opac' ) {
760
                    warn $_->error()." Redirecting to installer.";
761
                    print $query->redirect("/cgi-bin/koha/installer/install.pl");
762
                } else {
763
                    warn $_->error()." Redirecting to maintenance.";
764
                    print $query->redirect("/cgi-bin/koha/maintenance.pl");
765
                }
766
                safe_exit;
767
768
            }
769
            elsif ($_->isa('Koha::Exception::LoginFailed')) {
770
                #TODO:: Return proper legacy values to get_template_and_user() when login fails.
771
            }
772
            elsif ($_->isa('Koha::Exception::ServiceTemporarilyUnavailable')) {
773
                warn $_->error();
774
                print $query->redirect("/cgi-bin/koha/maintenance.pl");
775
                safe_exit;
776
            }
777
            else {
778
                warn "Unknown exception class ".ref($_)."\n";
779
                die $_; #Unhandled exception case
780
            }
781
        }
782
        else {
783
            die $_; #Not a Koha::Exception-object
784
        }
785
    };
786
787
    return checkauth_legacy(@params);
788
}
789
790
=head checkauth_legacy
791
@DEPRECATED See Bug 7174
792
793
We are calling this because the given authentication mechanism is not yet supported
794
in Koha::Auth.
795
796
See checkauth-documentation floating somewhere in this file for info about the
797
legacy authentication.
798
=cut
799
800
sub checkauth_legacy {
706
    my $query = shift;
801
    my $query = shift;
707
    $debug and warn "Checking Auth";
802
    $debug and warn "Checking Auth";
708
803
Lines 1682-1693 sub get_session { Link Here
1682
    return $session;
1777
    return $session;
1683
}
1778
}
1684
1779
1780
#@DEPRECATED See Bug 7174
1685
sub checkpw {
1781
sub checkpw {
1686
    my ( $dbh, $userid, $password, $query, $type ) = @_;
1782
    my ( $dbh, $userid, $password, $query, $type ) = @_;
1687
    $type = 'opac' unless $type;
1783
    $type = 'opac' unless $type;
1688
    if ($ldap) {
1784
    if ($ldap) {
1689
        $debug and print STDERR "## checkpw - checking LDAP\n";
1785
        $debug and print STDERR "## checkpw - checking LDAP\n";
1690
        my ( $retval, $retcard, $retuserid ) = checkpw_ldap(@_);    # EXTERNAL AUTH
1786
        my ( $retval, $retcard, $retuserid ) = checkpw_ldap($userid, $password);    # EXTERNAL AUTH
1691
        return 0 if $retval == -1;                                  # Incorrect password for LDAP login attempt
1787
        return 0 if $retval == -1;                                  # Incorrect password for LDAP login attempt
1692
        ($retval) and return ( $retval, $retcard, $retuserid );
1788
        ($retval) and return ( $retval, $retcard, $retuserid );
1693
    }
1789
    }
Lines 1726-1731 sub checkpw { Link Here
1726
    return checkpw_internal(@_)
1822
    return checkpw_internal(@_)
1727
}
1823
}
1728
1824
1825
#@DEPRECATED See Bug 7174
1729
sub checkpw_internal {
1826
sub checkpw_internal {
1730
    my ( $dbh, $userid, $password ) = @_;
1827
    my ( $dbh, $userid, $password ) = @_;
1731
1828
Lines 1778-1783 sub checkpw_internal { Link Here
1778
            return 1, $cardnumber, $userid;
1875
            return 1, $cardnumber, $userid;
1779
        }
1876
        }
1780
    }
1877
    }
1878
1879
    #@DEPRECATED see Bug 7174. I think the demo-user should be represented with permissions instead of a hard-coded non-borrower anomaly.
1781
    if ( $userid && $userid eq 'demo'
1880
    if ( $userid && $userid eq 'demo'
1782
        && "$password" eq 'demo'
1881
        && "$password" eq 'demo'
1783
        && C4::Context->config('demo') )
1882
        && C4::Context->config('demo') )
Lines 1790-1795 sub checkpw_internal { Link Here
1790
    return 0;
1889
    return 0;
1791
}
1890
}
1792
1891
1892
#@DEPRECATED See Bug 7174
1793
sub checkpw_hash {
1893
sub checkpw_hash {
1794
    my ( $password, $stored_hash ) = @_;
1894
    my ( $password, $stored_hash ) = @_;
1795
1895
Lines 1800-1805 sub checkpw_hash { Link Here
1800
    if ( substr( $stored_hash, 0, 2 ) eq '$2' ) {
1900
    if ( substr( $stored_hash, 0, 2 ) eq '$2' ) {
1801
        $hash = hash_password( $password, $stored_hash );
1901
        $hash = hash_password( $password, $stored_hash );
1802
    } else {
1902
    } else {
1903
        #@DEPRECATED Digest::MD5, don't use it or you will get hurt.
1803
        $hash = md5_base64($password);
1904
        $hash = md5_base64($password);
1804
    }
1905
    }
1805
    return $hash eq $stored_hash;
1906
    return $hash eq $stored_hash;
(-)a/C4/Auth_with_ldap.pm (-1 / +10 lines)
Lines 103-110 sub search_method { Link Here
103
    return $search;
103
    return $search;
104
}
104
}
105
105
106
=head checkpw_ldap
107
108
@RETURNS Integer, -1 if login failed
109
                , 0 if connection to the LDAP server couldn't be reliably established.
110
		 or List of (-1|1|0, $cardnumber, $local_userid);
111
		 where $cardnumber is koha.borrowers.cardnumber
112
		       $local_userid is the koha.borrowers.userid
113
=cut
114
106
sub checkpw_ldap {
115
sub checkpw_ldap {
107
    my ($dbh, $userid, $password) = @_;
116
    my ($userid, $password) = @_;
108
    my @hosts = split(',', $prefhost);
117
    my @hosts = split(',', $prefhost);
109
    my $db = Net::LDAP->new(\@hosts);
118
    my $db = Net::LDAP->new(\@hosts);
110
    unless ( $db ) {
119
    unless ( $db ) {
(-)a/Koha/Auth.pm (+187 lines)
Line 0 Link Here
1
package Koha::Auth;
2
3
# Copyright 2015 Vaara-kirjastot
4
#
5
# This file is part of Koha.
6
#
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.
11
#
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.
16
#
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>.
19
20
#Define common packages
21
use Modern::Perl;
22
use Scalar::Util qw(blessed);
23
use Try::Tiny;
24
25
#Define Koha packages
26
use Koha::Auth::Route::Password;
27
28
#Define Exceptions
29
use Koha::Exception::BadParameter;
30
31
#Define the headers, POST-parameters and cookies extracted from the various web-frameworks'
32
# request-objects and passed to the authentication system as normalized values.
33
our @authenticationHeaders = ('X-Koha-Signature', 'ETag', 'X-Koha-Username');
34
our @authenticationPOSTparams = ('password', 'userid', 'PT');
35
our @authenticationCookies = ('CGISESSID'); #Really we should have only one of these.
36
37
sub authenticate {
38
    my ($controller, $permissions, $authParams) = @_;
39
    my ($headers, $postParams, $cookies) = _authenticate_validateAndNormalizeParameters(@_);
40
41
    my $borrower; #Each authentication route returns a Koha::Borrower-object on success. We use this to generate the Context()
42
43
    ##Select the Authentication route.
44
    ##Routes are introduced in priority order, and if one matches, the other routes are ignored.
45
    try {
46
        #1. Check for password authentication, including LDAP.
47
        if ($postParams->{userid} && $postParams->{password}) {
48
            $borrower = Koha::Auth::Route::Password::check($headers, $postParams, $cookies, $permissions);
49
        }
50
        #2. Check for REST's signature-based authentication.
51
        elsif ($headers->{'X-Koha-Signature'}) {
52
            $borrower = Koha::Auth::Route::REST::V1::check($headers, $postParams, $cookies, $permissions);
53
        }
54
        #3. Check for the cookie. If cookies go stale, they block all subsequent authentication methods, so keep it down.
55
        elsif ($cookies) {
56
            $borrower = Koha::Auth::Route::Cookie::check($headers, $postParams, $cookies, $permissions);
57
        }
58
        else { #HTTP CAS ticket or shibboleth or Persona not implemented
59
            ##Backwards compatibility: give the fallback signal to use the legacy authentication mechanism
60
            return 'FALLBACK';
61
        }
62
    } catch {
63
        if (blessed($_)) {
64
            if ($_->isa('Koha::Exception::LoginFailed')) {
65
                if ($authParams->{authnotrequired}) { #We failed to login, but we can continue anonymously.
66
                    $borrower = Koha::Borrower->new();
67
                }
68
                else {
69
                    $_->rethrow(); #Anonymous login not enabled this time
70
                }
71
            }
72
            else {
73
                $_->rethrow(); #Propagate other errors to the calling Controller to redirect as it wants.
74
            }
75
        }
76
        else {
77
            die $_; #Not a Koha::Exception-object
78
        }
79
    };
80
81
    setUserEnvironment($borrower);
82
}
83
84
=head _authenticate_validateAndNormalizeParameters
85
86
@THROWS Koha::Exception::BadParameter, if validating parameters fails.
87
=cut
88
89
sub _authenticate_validateAndNormalizeParameters {
90
    my ($controller, $permissions, $authParams) = @_;
91
92
    #Validate $controller.
93
    my ($headers, $postParams, $cookies);
94
    if (blessed($controller) && $controller->isa('CGI')) {
95
        ($headers, $postParams, $cookies) = _normalizeCGI($controller);
96
    }
97
    elsif (blessed($controller) && $controller->isa('Mojolicious::Controller')) {
98
        ($headers, $postParams, $cookies) = _normalizeMojolicious($controller);
99
    }
100
    else {
101
        Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The first parameter MUST be either a 'CGI'-object or a 'Mojolicious::Controller'-object");
102
    }
103
    #Validate $permissions 
104
    unless (not($permissions) || (ref $permissions eq 'HASH')) {
105
        Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The second parameter MUST be 'undef' or a HASHRef of Koha permissions. See C4::Auth::haspermission().");
106
    }
107
    #Validate $authParams
108
    unless (not($authParams) || (ref $authParams eq 'HASH')) {
109
        Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The third parameter MUST be 'undef' or a HASHRef.");
110
    }
111
112
    return ($headers, $postParams, $cookies);
113
}
114
115
=head _normalizeCGI
116
Takes a CGI-object and finds the authentication markers from it.
117
@PARAM1 CGI-object.
118
@RETURNS List of : HASHRef of headers required for authentication, or undef
119
                   HASHRef of POST parameters required for authentication, or undef
120
                   String of the authenticaton cookie value, or undef
121
=cut
122
123
sub _normalizeCGI {
124
    my ($controller) = @_;
125
126
    my ($headers, $postParams, $cookies);
127
    if (blessed($controller) && $controller->isa('CGI')) {
128
        foreach my $authHeader (@authenticationHeaders) {
129
            if (my $val = $controller->http($authHeader)) {
130
                $headers->{$authHeader} = $val;
131
            }
132
        }
133
        foreach my $authParam (@authenticationPOSTparams) {
134
            if (my $val = $controller->param($authParam)) {
135
                $postParams->{$authParam} = $val;
136
            }
137
        }
138
        foreach my $authCookie (@authenticationCookies) {
139
            if (my $val = $controller->param($authCookie)) {
140
                $cookies->{$authCookie} = $val;
141
            }
142
        }
143
    }
144
    return ($headers, $postParams, $cookies);
145
}
146
147
=head _normalizeMojolicious
148
Takes a Mojolicious::Controller-object and finds the authentication markers from it.
149
@PARAM1 Mojolicious::Controller-object.
150
@RETURNS List of : HASHRef of headers required for authentication, or undef
151
                   HASHRef of POST parameters required for authentication, or undef
152
                   String of the authenticaton cookie value, or undef
153
=cut
154
155
sub _normalizeMojolicious {
156
    my ($controller) = @_;
157
158
    my $request = $controller->req();
159
    my ($headers, $postParams, $cookies);
160
    if (blessed($controller) && $controller->isa('CGI')) {
161
        my $headers = $request->headers();
162
        foreach my $authHeader (@authenticationHeaders) {
163
            if (my $val = $request->headers()->$authHeader()) {
164
                $headers->{$authHeader} = $val;
165
            }
166
        }
167
        foreach my $authParam (@authenticationPOSTparams) {
168
            if (my $val = $request->param($authParam)) {
169
                $postParams->{$authParam} = $val;
170
            }
171
        }
172
        foreach my $authCookie (@authenticationCookies) {
173
            if (my $val = $request->cookies($authCookie)) {
174
                $cookies->{$authCookie} = $val;
175
            }
176
        }
177
    }
178
    return ($headers, $postParams, $cookies);
179
}
180
=head setUserEnvironment
181
Set the C4::Context::user_env()
182
=cut
183
184
sub setUserEnvironment {
185
    my ($borrower) = @_;
186
}
187
1;
(-)a/Koha/Auth/Component.pm (+153 lines)
Line 0 Link Here
1
package Koha::Auth::Component;
2
3
# Copyright 2015 Vaara-kirjastot
4
#
5
# This file is part of Koha.
6
#
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.
11
#
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.
16
#
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>.
19
20
use Modern::Perl;
21
22
use Koha qw(version);
23
use Koha::Borrowers;
24
use Koha::AuthUtils;
25
26
use Koha::Exception::VersionMismatch;
27
use Koha::Exception::BadSystemPreference;
28
use Koha::Exception::ServiceTemporarilyUnavailable;
29
30
=head1 NAME Koha::Auth::Component
31
32
=head2 SYNOPSIS
33
34
In this package we define the authentication steps we can use to define
35
authentication path behaviour.
36
37
=head2 USAGE
38
39
    use Scalar::Util qw(blessed);
40
    try {
41
        ...
42
        Koha::Auth::Component::checkVersion();
43
        Koha::Auth::Component::checkOPACMaintenance();
44
        ...
45
    } catch {
46
        if (blessed($_)) {
47
            if ($_->isa('Koha::Exception::VersionMismatch')) {
48
                ##handle exception
49
            }
50
            elsif ($_->isa('Koha::Exception::AnotherKindOfException')) {
51
                ...
52
            }
53
            ...
54
            else {
55
                warn "Unknown exception class ".ref($_)."\n";
56
                die $_; #Unhandled exception case
57
            }
58
        }
59
        else {
60
            die $_; #Not a Koha::Exception-object
61
        }
62
    };
63
64
=cut
65
66
=head checkVersion
67
STATIC
68
69
    Koha::Auth::Component::checkVersion();
70
71
Checks if the DB version is valid.
72
73
@THROWS Koha::Exception::VersionMismatch, if versions do not match
74
@THROWS Koha::Exception::BadSystemPreference, if "Version"-syspref is not set.
75
                        This probably means that Koha has not been installed yet.
76
=cut
77
78
sub checkVersion {
79
    my $versionSyspref = C4::Context->preference('Version');
80
    unless ( $versionSyspref ) {
81
        Koha::Exception::BadSystemPreference->throw(error => "No Koha 'Version'-system preference defined. Koha needs to be installed.");
82
    }
83
84
    my $kohaversion = Koha::version();
85
    # remove the 3 last . to have a Perl number
86
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
87
    if ( $versionSyspref < $kohaversion ) {
88
        Koha::Exception::VersionMismatch->throw(error => "Database update needed. Database is 'v$versionSyspref' and Koha is 'v$kohaversion'");
89
    }
90
}
91
92
=head checkOPACMaintenance
93
STATIC
94
95
    Koha::Auth::Component::checkOPACMaintenance();
96
97
Checks if OPAC is under maintenance.
98
99
@THROWS Koha::Exception::ServiceTemporarilyUnavailable
100
=cut
101
102
sub checkOPACMaintenance {
103
    if ( C4::Context->preference('OpacMaintenance') ) {
104
        Koha::Exception::ServiceTemporarilyUnavailable->throw(error => 'OPAC is under maintenance');
105
    }
106
}
107
108
=head checkPassword
109
STATIC
110
111
    Koha::Auth::Component::checkPassword();
112
113
@RETURN Koha::Borrower-object if check succeedes, otherwise throws exceptions.
114
@THROWS Koha::Exception::LoginFailed from Koha::AuthUtils password checks.
115
=cut
116
117
sub checkPassword {
118
    my ($userid, $password) = @_;
119
120
    my $borrower;
121
    if (C4::Context->config('useldapserver')) {
122
        $borrower = Koha::AuthUtils::checkLDAPPassword();
123
        return $borrower if $borrower;
124
    }
125
    elsif (C4::Context->preference('casAuthentication')) {
126
        warn("Koha::Auth doesn't support CAS-authentication yet. Please refactor the CAS client implementation to work with Koha::Auth. It cant be too hard :)");
127
    }
128
    elsif (C4::Context->config('useshibboleth')) {
129
        warn("Koha::Auth doesn't support Shibboleth-authentication yet. Please refactor the Shibboleth client implementation to work with Koha::Auth. It cant be too hard :)");
130
    }
131
132
    return Koha::AuthUtils::checkKohaPassword($userid, $password);
133
}
134
135
=head checkPermissions
136
STATIC
137
138
    Koha::Auth::Component::checkPermissions($borrower, $permissionsRequired);
139
140
@THROWS Koha::Exception::LoginFailed with the missing permission if permissions
141
                are inadequate
142
=cut
143
144
sub checkPermissions {
145
    my ($borrower, $permissionsRequired) = @_;
146
147
    my ($failedPermission, $flags) = C4::Auth::haspermission($borrower, $permissionsRequired);
148
    if ($failedPermission) {
149
        Koha::Exception::LoginFailed->throw(error => "Missing permission '$failedPermission'.");
150
    }
151
}
152
153
1;
(-)a/Koha/Auth/Route.pm (+65 lines)
Line 0 Link Here
1
package Koha::Auth::Route;
2
3
# Copyright 2015 Vaara-kirjastot
4
#
5
# This file is part of Koha.
6
#
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.
11
#
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.
16
#
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>.
19
20
=head
21
22
=NAME Koha::Auth::Route
23
24
=SYNOPSIS
25
26
This is an interface definition for Koha::Auth::Route::* -subclasses.
27
This documentation explains how to subclass different routes.
28
29
=USAGE
30
31
    if ($userid && $password) {
32
        $borrower = Koha::Auth::Route::<RouteName>::challenge($headers, $postParams, $cookies);
33
    }
34
35
=head INPUT
36
37
Each Route gets three parameters:
38
    $headers:       HASHRef of HTTP Headers matching the @authenticationHeaders-package
39
                    variable in Koha::Auth,
40
                    Eg. { 'X-Koha-Signature' => "23in4ow2gas2opcnpa", ... }
41
    $postParams:    HASHRef of HTTP POST parameters matching the
42
                    @authenticationPOSTparams-package variable in Koha::Auth,
43
                    Eg. { password => '1234', 'userid' => 'admin'}
44
    $cookies:       HASHRef of HTTP Cookies matching the
45
                    @authenticationPOSTparams-package variable in Koha::Auth,
46
                    EG. { CGISESSID => '9821rj1kn3tr9ff2of2ln1' }
47
    $permissions:   HASHRef of Koha permissions.
48
                    See C4::Auth::haspermission() for example.
49
50
=head OUTPUT
51
52
Each route must return a Koha::Borrower-object representing the authenticated user.
53
Even if the login succeeds with a superuser or similar virtual user, like
54
anonymous login, a mock Borrower-object must be returned.
55
If the login fails, each route must throw Koha::Exceptions to notify the cause
56
of the failure.
57
58
=head ROUTE STRUCTURE
59
60
Each route consists of Koha::Auth::Component-subroutine calls to test for various
61
authentication challenges.
62
63
=cut
64
65
1;
(-)a/Koha/Auth/Route/Password.pm (+39 lines)
Line 0 Link Here
1
package Koha::Auth::Route::Password;
2
3
# Copyright 2015 Vaara-kirjastot
4
#
5
# This file is part of Koha.
6
#
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.
11
#
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.
16
#
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>.
19
20
use Modern::Perl;
21
22
use Koha::Auth::Component;
23
24
=head challenge
25
26
@THROWS Koha::Exceptions from authentication components.
27
=cut
28
29
sub challenge {
30
    my ($headers, $postParams, $cookies, $permissionsRequired) = @_;
31
32
    Koha::Auth::Component::checkOPACMaintenance();
33
    Koha::Auth::Component::checkVersion();
34
    my $borrower = Koha::Auth::Component::checkPassword($postParams->{userid}, $postParams->{password});
35
    Koha::Auth::Component::checkPermissions($borrower, $permissionsRequired) if $permissionsRequired;
36
    return $borrower;
37
}
38
39
1;
(-)a/Koha/AuthUtils.pm (+151 lines)
Lines 22-31 use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64); Link Here
22
use Encode qw( encode is_utf8 );
22
use Encode qw( encode is_utf8 );
23
use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt
23
use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt
24
24
25
use Koha::Borrowers;
26
27
use Koha::Exception::LoginFailed;
28
25
use base 'Exporter';
29
use base 'Exporter';
26
30
27
our $VERSION = '1.01';
31
our $VERSION = '1.01';
28
our @EXPORT_OK   = qw(hash_password);
32
our @EXPORT_OK   = qw(hash_password);
33
our @usernameAliasColumns = ('userid', 'cardnumber'); #Possible columns to treat as the username when authenticating. Must be UNIQUE in DB.
29
34
30
=head1 NAME
35
=head1 NAME
31
36
Lines 133-138 sub generate_salt { Link Here
133
    close SOURCE;
138
    close SOURCE;
134
    return $string;
139
    return $string;
135
}
140
}
141
142
=head checkKohaPassword
143
144
    my $borrower = Koha::AuthUtils::checkKohaPassword($userid, $password);
145
146
Checks if the given username and password match anybody in the Koha DB
147
@PARAM1 String, user identifier, either the koha.borrowers.userid, or koha.borrowers.cardnumber
148
@PARAM2 String, clear text password from the authenticating user
149
@RETURN Koha::Borrower, if login succeeded.
150
                Sets Koha::Borrower->isSuperuser() if the user is a superuser.
151
@THROWS Koha::Exception::LoginFailed, if no matching password was found for all username aliases in Koha.
152
=cut
153
154
sub checkKohaPassword {
155
    my ($userid, $password) = @_;
156
    my $borrower; #Find the borrower to return
157
158
    $borrower = _checkKohaSuperuser($userid, $password);
159
    return $borrower if $borrower;
160
161
    my $usernameFound = 0; #Report to the user if userid/barcode was found, even if the login failed.
162
    #Check for each username alias if we can confirm a login with that.
163
    for my $unameAlias (@usernameAliasColumns) {
164
        my $borrower = Koha::Borrowers->find({$unameAlias => $userid});
165
        if ( $borrower ) {
166
            $usernameFound = 1;
167
            return $borrower if ( checkHash( $password, $borrower->password ) );
168
        }
169
    }
170
171
    Koha::Exception::LoginFailed->throw(error => "Password authentication failed for the given ".( ($usernameFound) ? "password" : "username and password").".");
172
}
173
174
=head checkHash
175
176
    my $passwordOk = Koha::AuthUtils::checkHash($password1, $password2)
177
178
Checks if a clear-text String/password matches the given hash when
179
MD5 or Bcrypt hashing algorith is applied to it.
180
181
Bcrypt is applied if @PARAM2 starts with '$2'
182
MD5 otherwise
183
184
@PARAM1 String, clear text passsword or any other String
185
@PARAM2 String, hashed text password or any other String.
186
@RETURN Boolean, 1 if given parameters match
187
               , 0 if not
188
=cut
189
190
sub checkHash {
191
    my ( $password, $stored_hash ) = @_;
192
193
    $password = Encode::encode( 'UTF-8', $password )
194
            if Encode::is_utf8($password);
195
196
    return if $stored_hash eq '!';
197
198
    my $hash;
199
    if ( substr( $stored_hash, 0, 2 ) eq '$2' ) {
200
        $hash = hash_password( $password, $stored_hash );
201
    } else {
202
        #@DEPRECATED Digest::MD5, don't use it or you will get hurt.
203
        require Digest::MD5;
204
        $hash = Digest::MD5::md5_base64($password);
205
    }
206
    return $hash eq $stored_hash;
207
}
208
209
=head checkLDAPPassword
210
211
Checks if the given username and password match anybody in the LDAP service
212
@PARAM1 String, user identifier
213
@PARAM2 String, clear text password from the authenticating user
214
@RETURN Koha::Borrower, or
215
            undef if we couldn't reliably contact the LDAP server so we should
216
            fallback to local Koha Password authentication.
217
@THROWS Koha::Exception::LoginFailed, if LDAP login failed
218
=cut
219
220
sub checkLDAPPassword {
221
    my ($userid, $password) = @_;
222
223
    #Lazy load dependencies because somebody might never need them.
224
    require C4::Auth_with_ldap;
225
226
    my ($retval, $cardnumber, $local_userid) = C4::Auth_with_ldap::checkpw_ldap($userid, $password);    # EXTERNAL AUTH
227
    if ($retval == -1) {
228
        Koha::Exception::LoginFailed->throw(error => "LDAP authentication failed for the given username and password");
229
    }
230
231
    if ($retval) {
232
        my $borrower = Koha::Borrower->find({userid => $local_userid});
233
        return $borrower;
234
    }
235
    return undef;
236
}
237
238
=head _checkKohaSuperuser
239
240
    my $borrower = Koha::AuthUtils::_checkKohaSuperuser($userid, $password);
241
242
Check if the userid and password match the ones in the $KOHA_CONF
243
@PARAM1 String, user identifier, either the koha.borrowers.userid, or koha.borrowers.cardnumber
244
@PARAM2 String, clear text password from the authenticating user
245
@RETURNS Koha::Borrower branded as superuser with ->isSuperuser()
246
         or undef if user logging in is not a superuser.
247
@THROWS Koha::Exception::LoginFailed if user identifier matches, but password doesn't
248
=cut
249
250
sub _checkKohaSuperuser {
251
    my ($userid, $password) = @_;
252
253
    if ( $userid && $userid eq C4::Context->config('user') ) {
254
        if ( $password && $password eq C4::Context->config('pass') ) {
255
            return _createTemporarySuperuser();
256
        }
257
        else {
258
            Koha::Exception::LoginFailed->throw(error => "Password authentication failed");
259
        }
260
    }
261
}
262
263
=head _createTemporarySuperuser
264
265
Create a temporary superuser which should be instantiated only to the environment
266
and then discarded. So do not ->store() it!
267
@RETURN Koha::Borrower, stamped as superuser.
268
=cut
269
270
sub _createTemporarySuperuser {
271
    my $borrower = Koha::Borrower->new();
272
273
    my $superuserName = C4::Context->config('user');
274
    $borrower->isSuperuser(1);
275
    $borrower->set({borrowernumber => 0,
276
                       userid     => $superuserName,
277
                       cardnumber => $superuserName,
278
                       firstname  => $superuserName,
279
                       surname    => $superuserName,
280
                       branchcode => 'NO_LIBRARY_SET',
281
                       flags      => 1,
282
                       email      => C4::Context->preference('KohaAdminEmailAddress')
283
                    });
284
    return $borrower;
285
}
286
136
1;
287
1;
137
288
138
__END__
289
__END__
(-)a/Koha/Borrower.pm (+26 lines)
Lines 43-48 sub type { Link Here
43
    return 'Borrower';
43
    return 'Borrower';
44
}
44
}
45
45
46
=head isSuperuser
47
48
    $borrower->isSuperuser(1); #Set this borrower to be a superuser
49
    if ($borrower->isSuperuser()) {
50
        #All your base are belong to us
51
    }
52
53
Should be used from the authentication modules to mark this $borrower-object to
54
have unlimited access to all Koha-features.
55
This $borrower-object is the Koha DB user.
56
@PARAM1 Integer, 1 means this borrower is the super/DB user.
57
                "0" disables the previously set superuserness.
58
=cut
59
60
sub isSuperuser {
61
    my ($self, $Iam) = @_;
62
63
    if (defined $Iam && $Iam == 1) {
64
        $self->{superuser} = 1;
65
    }
66
    elsif (defined $Iam && $Iam eq "0") { #Dealing with zero is special in Perl
67
        $self->{superuser} = undef;
68
    }
69
    return (exists($self->{superuser}) && $self->{superuser}) ? 1 : undef;
70
}
71
46
=head1 AUTHOR
72
=head1 AUTHOR
47
73
48
Kyle M Hall <kyle@bywatersolutions.com>
74
Kyle M Hall <kyle@bywatersolutions.com>
(-)a/t/db_dependent/Auth_with_ldap.t (-9 / +9 lines)
Lines 74-80 subtest "checkpw_ldap tests" => sub { Link Here
74
74
75
    ## Connection fail tests
75
    ## Connection fail tests
76
    $desired_connection_result = 'error';
76
    $desired_connection_result = 'error';
77
    warning_is { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) }
77
    warning_is { $ret = C4::Auth_with_ldap::checkpw_ldap( 'hola', password => 'hey' ) }
78
        "LDAP connexion failed",
78
        "LDAP connexion failed",
79
        "checkpw_ldap prints correct warning if LDAP conexion fails";
79
        "checkpw_ldap prints correct warning if LDAP conexion fails";
80
    is( $ret, 0, "checkpw_ldap returns 0 if LDAP conexion fails");
80
    is( $ret, 0, "checkpw_ldap returns 0 if LDAP conexion fails");
Lines 96-102 subtest "checkpw_ldap tests" => sub { Link Here
96
96
97
97
98
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
98
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
99
                               $dbh, 'hola', password => 'hey' ) }
99
                               'hola', password => 'hey' ) }
100
                    qr/Anonymous LDAP bind failed: LDAP error #1: error_name/,
100
                    qr/Anonymous LDAP bind failed: LDAP error #1: error_name/,
101
                    "checkpw_ldap prints correct warning if LDAP anonymous bind fails";
101
                    "checkpw_ldap prints correct warning if LDAP anonymous bind fails";
102
        is( $ret, 0, "checkpw_ldap returns 0 if LDAP anonymous bind fails");
102
        is( $ret, 0, "checkpw_ldap returns 0 if LDAP anonymous bind fails");
Lines 108-121 subtest "checkpw_ldap tests" => sub { Link Here
108
        $desired_count_result  = 0; # user auth problem
108
        $desired_count_result  = 0; # user auth problem
109
        $non_anonymous_bind_result = 'success';
109
        $non_anonymous_bind_result = 'success';
110
        reload_ldap_module();
110
        reload_ldap_module();
111
        is ( C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ),
111
        is ( C4::Auth_with_ldap::checkpw_ldap( 'hola', password => 'hey' ),
112
            0, "checkpw_ldap returns 0 if user lookup returns 0");
112
            0, "checkpw_ldap returns 0 if user lookup returns 0");
113
113
114
        $non_anonymous_bind_result = 'error';
114
        $non_anonymous_bind_result = 'error';
115
        reload_ldap_module();
115
        reload_ldap_module();
116
116
117
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
117
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
118
                               $dbh, 'hola', password => 'hey' ) }
118
                               'hola', password => 'hey' ) }
119
                    qr/LDAP bind failed as kohauser hola: LDAP error #1: error_name/,
119
                    qr/LDAP bind failed as kohauser hola: LDAP error #1: error_name/,
120
                    "checkpw_ldap prints correct warning if LDAP bind fails";
120
                    "checkpw_ldap prints correct warning if LDAP bind fails";
121
        is ( $ret, -1, "checkpw_ldap returns -1 LDAP bind fails for user (Bug 8148)");
121
        is ( $ret, -1, "checkpw_ldap returns -1 LDAP bind fails for user (Bug 8148)");
Lines 130-136 subtest "checkpw_ldap tests" => sub { Link Here
130
        reload_ldap_module();
130
        reload_ldap_module();
131
131
132
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
132
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
133
                               $dbh, 'hola', password => 'hey' ) }
133
                               'hola', password => 'hey' ) }
134
                    qr/LDAP bind failed as kohauser hola: LDAP error #1: error_name/,
134
                    qr/LDAP bind failed as kohauser hola: LDAP error #1: error_name/,
135
                    "checkpw_ldap prints correct warning if LDAP bind fails";
135
                    "checkpw_ldap prints correct warning if LDAP bind fails";
136
        is ( $ret, 0, "checkpw_ldap returns 0 LDAP bind fails for user (Bug 12831)");
136
        is ( $ret, 0, "checkpw_ldap returns 0 LDAP bind fails for user (Bug 12831)");
Lines 150-156 subtest "checkpw_ldap tests" => sub { Link Here
150
        reload_ldap_module();
150
        reload_ldap_module();
151
151
152
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
152
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
153
                               $dbh, 'hola', password => 'hey' ) }
153
                               'hola', password => 'hey' ) }
154
                    qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/,
154
                    qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/,
155
                    "checkpw_ldap prints correct warning if LDAP bind fails";
155
                    "checkpw_ldap prints correct warning if LDAP bind fails";
156
        is ( $ret, 0, "checkpw_ldap returns 0 if bind fails");
156
        is ( $ret, 0, "checkpw_ldap returns 0 if bind fails");
Lines 162-168 subtest "checkpw_ldap tests" => sub { Link Here
162
        reload_ldap_module();
162
        reload_ldap_module();
163
163
164
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
164
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
165
                               $dbh, 'hola', password => 'hey' ) }
165
                               'hola', password => 'hey' ) }
166
                    qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/,
166
                    qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/,
167
                    "checkpw_ldap prints correct warning if LDAP bind fails";
167
                    "checkpw_ldap prints correct warning if LDAP bind fails";
168
        is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)");
168
        is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)");
Lines 175-181 subtest "checkpw_ldap tests" => sub { Link Here
175
        reload_ldap_module();
175
        reload_ldap_module();
176
176
177
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
177
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
178
                               $dbh, 'hola', password => 'hey' ) }
178
                               'hola', password => 'hey' ) }
179
                    qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/,
179
                    qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/,
180
                    "checkpw_ldap prints correct warning if LDAP bind fails";
180
                    "checkpw_ldap prints correct warning if LDAP bind fails";
181
        is ( $ret, 0, "checkpw_ldap returns 0 if bind fails");
181
        is ( $ret, 0, "checkpw_ldap returns 0 if bind fails");
Lines 187-193 subtest "checkpw_ldap tests" => sub { Link Here
187
        reload_ldap_module();
187
        reload_ldap_module();
188
188
189
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
189
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
190
                               $dbh, 'hola', password => 'hey' ) }
190
                               'hola', password => 'hey' ) }
191
                    qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/,
191
                    qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/,
192
                    "checkpw_ldap prints correct warning if LDAP bind fails";
192
                    "checkpw_ldap prints correct warning if LDAP bind fails";
193
        is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)");
193
        is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)");
(-)a/t/db_dependent/Koha/Borrower.t (-1 / +55 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright 2015 Open Source Freedom Fighters
4
#
5
# This file is part of Koha.
6
#
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.
11
#
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.
16
#
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>.
19
20
use Test::More; #Please don't set the test count here. It is nothing but trouble when rebasing against master and is of dubious help.
21
22
use Koha::Borrower;
23
24
25
26
testIsSuperuser();
27
28
29
30
31
32
################################################################################
33
#### Define test subroutines here ##############################################
34
################################################################################
35
36
=head testIsSuperuser
37
@UNIT_TEST
38
Tests Koha::Borrower->isSuperuser()
39
=cut
40
41
sub testIsSuperuser {
42
    my $borrower = Koha::Borrower->new();
43
    ok((not(defined($borrower->isSuperuser()))), "isSuperuser(): By default user is not defined as superuser.");
44
    ok(($borrower->isSuperuser(1) == 1), "isSuperuser(): Setting user as superuser returns 1.");
45
    ok(($borrower->isSuperuser() == 1), "isSuperuser(): Getting superuser status from a superuser returns 1.");
46
    ok((not(defined($borrower->isSuperuser(0)))), "isSuperuser(): Removing superuser status from a superuser OK and returns undef");
47
    ok((not(defined($borrower->isSuperuser()))), "isSuperuser(): Ex-superuser superuser status is undef");
48
}
49
50
51
52
53
#######################
54
done_testing(); #YAY!!
55
#######################

Return to bug 7174