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

(-)a/C4/Auth.pm (-93 / +197 lines)
Lines 19-25 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;
Lines 34-39 use C4::Branch; # GetBranches Link Here
34
use C4::Search::History;
34
use C4::Search::History;
35
use Koha;
35
use Koha;
36
use Koha::AuthUtils qw(hash_password);
36
use Koha::AuthUtils qw(hash_password);
37
use Koha::Auth;
38
use Koha::Auth::Component;
37
use POSIX qw/strftime/;
39
use POSIX qw/strftime/;
38
use List::MoreUtils qw/ any /;
40
use List::MoreUtils qw/ any /;
39
use Encode qw( encode is_utf8);
41
use Encode qw( encode is_utf8);
Lines 636-685 has authenticated. Link Here
636
638
637
=cut
639
=cut
638
640
641
=head _version_check
642
#@DEPRECATED See Bug 7174
643
use Koha::Auth::Component::* instead
644
=cut
645
639
sub _version_check {
646
sub _version_check {
647
    #@DEPRECATED See Bug 7174
640
    my $type  = shift;
648
    my $type  = shift;
641
    my $query = shift;
649
    my $query = shift;
642
    my $version;
650
    my $version;
643
651
644
    # If version syspref is unavailable, it means Koha is being installed,
652
    try {
645
    # and so we must redirect to OPAC maintenance page or to the WebInstaller
653
        Koha::Auth::Component::checkOPACMaintenance() if ( $type eq 'opac' );
646
    # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
654
        Koha::Auth::Component::checkVersion();
647
    if ( C4::Context->preference('OpacMaintenance') && $type eq 'opac' ) {
655
    } catch {
648
        warn "OPAC Install required, redirecting to maintenance";
656
        if (blessed($_)) {
649
        print $query->redirect("/cgi-bin/koha/maintenance.pl");
657
            if ($_->isa('Koha::Exception::VersionMismatch')) {
650
        safe_exit;
658
                # check that database and koha version are the same
651
    }
659
                # there is no DB version, it's a fresh install,
652
    unless ( $version = C4::Context->preference('Version') ) {    # assignment, not comparison
660
                # go to web installer
653
        if ( $type ne 'opac' ) {
661
                # there is a DB version, compare it to the code version
654
            warn "Install required, redirecting to Installer";
662
                my $warning = $_->error()." Redirecting to %s.";
655
            print $query->redirect("/cgi-bin/koha/installer/install.pl");
663
                if ( $type ne 'opac' ) {
656
        } else {
664
                    warn sprintf( $warning, 'Installer' );
657
            warn "OPAC Install required, redirecting to maintenance";
665
                    print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure");
658
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
666
                } else {
667
                    warn sprintf( "OPAC: " . $warning, 'maintenance' );
668
                    print $query->redirect("/cgi-bin/koha/maintenance.pl");
669
                }
670
                safe_exit;
671
            }
672
            elsif ($_->isa('Koha::Exception::ServiceTemporarilyUnavailable')) {
673
                # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
674
                warn "OPAC Install required, redirecting to maintenance";
675
                print $query->redirect("/cgi-bin/koha/maintenance.pl");
676
                safe_exit;
677
            }
678
            elsif ($_->isa('Koha::Exception::BadSystemPreference')) {
679
                # If version syspref is unavailable, it means Koha is being installed,
680
                # and so we must redirect to OPAC maintenance page or to the WebInstaller
681
                if ( $type ne 'opac' ) {
682
                    warn "Install required, redirecting to Installer";
683
                    print $query->redirect("/cgi-bin/koha/installer/install.pl");
684
                } else {
685
                    warn "OPAC Install required, redirecting to maintenance";
686
                    print $query->redirect("/cgi-bin/koha/maintenance.pl");
687
                }
688
                safe_exit;
689
            }
690
            else {
691
                warn "Unknown exception class ".ref($_)."\n";
692
                die $_->rethrow(); #Unhandled exception case
693
            }
659
        }
694
        }
660
        safe_exit;
695
        else {
661
    }
696
            die $_; #Not a Koha::Exception-object, so rethrow it
662
663
    # check that database and koha version are the same
664
    # there is no DB version, it's a fresh install,
665
    # go to web installer
666
    # there is a DB version, compare it to the code version
667
    my $kohaversion = Koha::version();
668
669
    # remove the 3 last . to have a Perl number
670
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
671
    $debug and print STDERR "kohaversion : $kohaversion\n";
672
    if ( $version < $kohaversion ) {
673
        my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
674
        if ( $type ne 'opac' ) {
675
            warn sprintf( $warning, 'Installer' );
676
            print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure");
677
        } else {
678
            warn sprintf( "OPAC: " . $warning, 'maintenance' );
679
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
680
        }
697
        }
681
        safe_exit;
698
    };
682
    }
683
}
699
}
684
700
685
sub _session_log {
701
sub _session_log {
Lines 699-705 sub _timeout_syspref { Link Here
699
    return $timeout;
715
    return $timeout;
700
}
716
}
701
717
718
=head checkauth
719
720
Compatibility layer for old Koha authentication system.
721
Tries to authenticate using Koha::Auth, but if no authentication mechanism is
722
identified, falls back to the deprecated legacy behaviour.
723
=cut
724
702
sub checkauth {
725
sub checkauth {
726
    my @params = @_; #Clone params so we don't accidentally change them if we fallback to checkauth_legacy()
727
    my $query = shift;
728
    my $authnotrequired = shift;
729
    my $flagsrequired   = _changeAllPermissionsMarkerToAnyPermissionMarker(shift); #circulate => 1 is deprecated, only circulate => '*' is supported.
730
    my $type            = shift;
731
    my $persona         = shift;
732
733
    ##Revert to legacy authentication for more complex authentication mechanisms.
734
    if ($persona && $cas && $shib) {
735
        return checkauth_legacy(@params);
736
    }
737
738
    my ($borrower, $cookie);
739
    try {
740
        ($borrower, $cookie) = Koha::Auth::authenticate($query, $flagsrequired, {authnotrequired => $authnotrequired});
741
    } catch {
742
        if (blessed($_)) {
743
            if ($_->isa('Koha::Exception::VersionMismatch')) {
744
745
                my $warning = $_->error()." Redirecting to %s.";
746
                if ( $type ne 'opac' ) {
747
                    warn sprintf( $warning, 'Installer' );
748
                    print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure");
749
                } else {
750
                    warn sprintf( "OPAC: " . $warning, 'maintenance' );
751
                    print $query->redirect("/cgi-bin/koha/maintenance.pl");
752
                }
753
                safe_exit;
754
755
            }
756
            elsif ($_->isa('Koha::Exception::BadSystemPreference')) {
757
758
                if ( $type ne 'opac' ) {
759
                    warn $_->error()." Redirecting to installer.";
760
                    print $query->redirect("/cgi-bin/koha/installer/install.pl");
761
                } else {
762
                    warn $_->error()." Redirecting to maintenance.";
763
                    print $query->redirect("/cgi-bin/koha/maintenance.pl");
764
                }
765
                safe_exit;
766
767
            }
768
            elsif ($_->isa('Koha::Exception::LoginFailed')) {
769
                _showLoginPage($query, $type, {}, {invalid_username_or_password => 1}, undef);
770
            }
771
            elsif ($_->isa('Koha::Exception::NoPermission')) {
772
                _showLoginPage($query, $type, {}, {nopermission => 1}, undef);
773
            }
774
            elsif ($_->isa('Koha::Exception::Logout')) {
775
                _showLoginPage($query, $type, {}, {}, undef);
776
            }
777
            elsif ($_->isa('Koha::Exception::ServiceTemporarilyUnavailable')) {
778
                warn $_->error();
779
                print $query->redirect("/cgi-bin/koha/maintenance.pl");
780
                safe_exit;
781
            }
782
            else {
783
                warn "Unknown exception class ".ref($_)."\n";
784
                $_->rethrow(); #Unhandled exception case
785
            }
786
        }
787
        else {
788
            die $_; #Not a Koha::Exception-object
789
        }
790
    };
791
792
    if ($borrower) { #We authenticated succesfully! Emulate the legacy interface for get_template_and_user();
793
        my $user = $borrower->userid;
794
        my $sessionID = $cookie->{value}->[0];
795
        my $aa = $borrower->userid;
796
        my $flags = getuserflags(undef, $borrower->userid, undef) if $borrower->userid;
797
        return ( $user, $cookie, $sessionID, $flags );
798
    }
799
}
800
801
=head checkauth_legacy
802
@DEPRECATED See Bug 7174
803
804
We are calling this because the given authentication mechanism is not yet supported
805
in Koha::Auth.
806
807
See checkauth-documentation floating somewhere in this file for info about the
808
legacy authentication.
809
=cut
810
811
sub checkauth_legacy {
812
    #@DEPRECATED See Bug 7174
703
    my $query = shift;
813
    my $query = shift;
704
    $debug and warn "Checking Auth";
814
    $debug and warn "Checking Auth";
705
815
Lines 721-728 sub checkauth { Link Here
721
    my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
831
    my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
722
    my $logout = $query->param('logout.x');
832
    my $logout = $query->param('logout.x');
723
833
724
    my $anon_search_history;
725
726
    # This parameter is the name of the CAS server we want to authenticate against,
834
    # This parameter is the name of the CAS server we want to authenticate against,
727
    # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
835
    # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
728
    my $casparam = $query->param('cas');
836
    my $casparam = $query->param('cas');
Lines 783-789 sub checkauth { Link Here
783
            #if a user enters an id ne to the id in the current session, we need to log them in...
891
            #if a user enters an id ne to the id in the current session, we need to log them in...
784
            #first we need to clear the anonymous session...
892
            #first we need to clear the anonymous session...
785
            $debug and warn "query id = $q_userid but session id = $s_userid";
893
            $debug and warn "query id = $q_userid but session id = $s_userid";
786
            $anon_search_history = $session->param('search_history');
787
            $session->delete();
894
            $session->delete();
788
            $session->flush;
895
            $session->flush;
789
            C4::Context->_unset_userenv($sessionID);
896
            C4::Context->_unset_userenv($sessionID);
Lines 864-876 sub checkauth { Link Here
864
        #we initiate a session prior to checking for a username to allow for anonymous sessions...
971
        #we initiate a session prior to checking for a username to allow for anonymous sessions...
865
        my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
972
        my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
866
973
867
        # Save anonymous search history in new session so it can be retrieved
868
        # by get_template_and_user to store it in user's search history after
869
        # a successful login.
870
        if ($anon_search_history) {
871
            $session->param( 'search_history', $anon_search_history );
872
        }
873
874
        my $sessionID = $session->id;
974
        my $sessionID = $session->id;
875
        C4::Context->_new_userenv($sessionID);
975
        C4::Context->_new_userenv($sessionID);
876
        $cookie = $query->cookie(
976
        $cookie = $query->cookie(
Lines 989-1000 sub checkauth { Link Here
989
                    $info{'nopermission'} = 1;
1089
                    $info{'nopermission'} = 1;
990
                    C4::Context->_unset_userenv($sessionID);
1090
                    C4::Context->_unset_userenv($sessionID);
991
                }
1091
                }
992
                my ( $borrowernumber, $firstname, $surname, $userflags,
1092
                my ( $borrowernumber, $firstname, $surname,
993
                    $branchcode, $branchname, $branchprinter, $emailaddress );
1093
                    $branchcode, $branchname, $branchprinter, $emailaddress );
994
1094
995
                if ( $return == 1 ) {
1095
                if ( $return == 1 ) {
996
                    my $select = "
1096
                    my $select = "
997
                    SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
1097
                    SELECT borrowernumber, firstname, surname, borrowers.branchcode,
998
                    branches.branchname    as branchname,
1098
                    branches.branchname    as branchname,
999
                    branches.branchprinter as branchprinter,
1099
                    branches.branchprinter as branchprinter,
1000
                    email
1100
                    email
Lines 1017-1026 sub checkauth { Link Here
1017
                        }
1117
                        }
1018
                    }
1118
                    }
1019
                    if ( $sth->rows ) {
1119
                    if ( $sth->rows ) {
1020
                        ( $borrowernumber, $firstname, $surname, $userflags,
1120
                        ( $borrowernumber, $firstname, $surname,
1021
                            $branchcode, $branchname, $branchprinter, $emailaddress ) = $sth->fetchrow;
1121
                            $branchcode, $branchname, $branchprinter, $emailaddress ) = $sth->fetchrow;
1022
                        $debug and print STDERR "AUTH_3 results: " .
1122
                        $debug and print STDERR "AUTH_3 results: " .
1023
                          "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
1123
                          "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$branchcode,$emailaddress\n";
1024
                    } else {
1124
                    } else {
1025
                        print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
1125
                        print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
1026
                    }
1126
                    }
Lines 1036-1042 sub checkauth { Link Here
1036
                        $branchname = GetBranchName($branchcode);
1136
                        $branchname = GetBranchName($branchcode);
1037
                    }
1137
                    }
1038
                    my $branches = GetBranches();
1138
                    my $branches = GetBranches();
1039
                    if ( C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation') ) {
1139
                    if ( C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation') ) { #Why Autolocation cannot work without IndependetBranches??
1040
1140
1041
                        # we have to check they are coming from the right ip range
1141
                        # we have to check they are coming from the right ip range
1042
                        my $domain = $branches->{$branchcode}->{'branchip'};
1142
                        my $domain = $branches->{$branchcode}->{'branchip'};
Lines 1066-1072 sub checkauth { Link Here
1066
                    $session->param( 'surname',      $surname );
1166
                    $session->param( 'surname',      $surname );
1067
                    $session->param( 'branch',       $branchcode );
1167
                    $session->param( 'branch',       $branchcode );
1068
                    $session->param( 'branchname',   $branchname );
1168
                    $session->param( 'branchname',   $branchname );
1069
                    $session->param( 'flags',        $userflags );
1070
                    $session->param( 'emailaddress', $emailaddress );
1169
                    $session->param( 'emailaddress', $emailaddress );
1071
                    $session->param( 'ip',           $session->remote_addr() );
1170
                    $session->param( 'ip',           $session->remote_addr() );
1072
                    $session->param( 'lasttime',     time() );
1171
                    $session->param( 'lasttime',     time() );
Lines 1149-1154 sub checkauth { Link Here
1149
    #
1248
    #
1150
1249
1151
    # get the inputs from the incoming query
1250
    # get the inputs from the incoming query
1251
    _showLoginPage($query, $type, $cookie, \%info, $casparam);
1252
}
1253
1254
sub _showLoginPage {
1255
    my ($query, $type, $cookie, $info, $casparam) = @_;
1256
1152
    my @inputs = ();
1257
    my @inputs = ();
1153
    foreach my $name ( param $query) {
1258
    foreach my $name ( param $query) {
1154
        (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
1259
        (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
Lines 1200-1206 sub checkauth { Link Here
1200
        IntranetUserJS                        => C4::Context->preference("IntranetUserJS"),
1305
        IntranetUserJS                        => C4::Context->preference("IntranetUserJS"),
1201
        IndependentBranches                   => C4::Context->preference("IndependentBranches"),
1306
        IndependentBranches                   => C4::Context->preference("IndependentBranches"),
1202
        AutoLocation                          => C4::Context->preference("AutoLocation"),
1307
        AutoLocation                          => C4::Context->preference("AutoLocation"),
1203
        wrongip                               => $info{'wrongip'},
1308
        wrongip                               => $info->{'wrongip'},
1204
        PatronSelfRegistration                => C4::Context->preference("PatronSelfRegistration"),
1309
        PatronSelfRegistration                => C4::Context->preference("PatronSelfRegistration"),
1205
        PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1310
        PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1206
        persona                               => C4::Context->preference("Persona"),
1311
        persona                               => C4::Context->preference("Persona"),
Lines 1208-1214 sub checkauth { Link Here
1208
    );
1313
    );
1209
1314
1210
    $template->param( OpacPublic => C4::Context->preference("OpacPublic") );
1315
    $template->param( OpacPublic => C4::Context->preference("OpacPublic") );
1211
    $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1316
    $template->param( loginprompt => 1 ) unless $info->{'nopermission'};
1212
1317
1213
    if ( $type eq 'opac' ) {
1318
    if ( $type eq 'opac' ) {
1214
        require C4::VirtualShelves;
1319
        require C4::VirtualShelves;
Lines 1238-1244 sub checkauth { Link Here
1238
        }
1343
        }
1239
1344
1240
        $template->param(
1345
        $template->param(
1241
            invalidCasLogin => $info{'invalidCasLogin'}
1346
            invalidCasLogin => $info->{'invalidCasLogin'}
1242
        );
1347
        );
1243
    }
1348
    }
1244
1349
Lines 1254-1260 sub checkauth { Link Here
1254
        url         => $self_url,
1359
        url         => $self_url,
1255
        LibraryName => C4::Context->preference("LibraryName"),
1360
        LibraryName => C4::Context->preference("LibraryName"),
1256
    );
1361
    );
1257
    $template->param(%info);
1362
    $template->param(%$info);
1258
1363
1259
    #    $cookie = $query->cookie(CGISESSID => $session->id
1364
    #    $cookie = $query->cookie(CGISESSID => $session->id
1260
    #   );
1365
    #   );
Lines 1268-1273 sub checkauth { Link Here
1268
}
1373
}
1269
1374
1270
=head2 check_api_auth
1375
=head2 check_api_auth
1376
@DEPRECATED See Bug 7174
1271
1377
1272
  ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1378
  ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1273
1379
Lines 1302-1307 Possible return values in C<$status> are: Link Here
1302
=cut
1408
=cut
1303
1409
1304
sub check_api_auth {
1410
sub check_api_auth {
1411
    #@DEPRECATED See Bug 7174
1305
    my $query         = shift;
1412
    my $query         = shift;
1306
    my $flagsrequired = shift;
1413
    my $flagsrequired = shift;
1307
1414
Lines 1433-1449 sub check_api_auth { Link Here
1433
            if ( $return == 1 ) {
1540
            if ( $return == 1 ) {
1434
                my (
1541
                my (
1435
                    $borrowernumber, $firstname,  $surname,
1542
                    $borrowernumber, $firstname,  $surname,
1436
                    $userflags,      $branchcode, $branchname,
1543
                    $branchcode, $branchname,
1437
                    $branchprinter,  $emailaddress
1544
                    $branchprinter,  $emailaddress
1438
                );
1545
                );
1439
                my $sth =
1546
                my $sth =
1440
                  $dbh->prepare(
1547
                  $dbh->prepare(
1441
"select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname,branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where userid=?"
1548
"select borrowernumber, firstname, surname, borrowers.branchcode, branches.branchname as branchname,branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where userid=?"
1442
                  );
1549
                  );
1443
                $sth->execute($userid);
1550
                $sth->execute($userid);
1444
                (
1551
                (
1445
                    $borrowernumber, $firstname,  $surname,
1552
                    $borrowernumber, $firstname,  $surname,
1446
                    $userflags,      $branchcode, $branchname,
1553
                    $branchcode, $branchname,
1447
                    $branchprinter,  $emailaddress
1554
                    $branchprinter,  $emailaddress
1448
                ) = $sth->fetchrow if ( $sth->rows );
1555
                ) = $sth->fetchrow if ( $sth->rows );
1449
1556
Lines 1454-1467 sub check_api_auth { Link Here
1454
                    $sth->execute($cardnumber);
1561
                    $sth->execute($cardnumber);
1455
                    (
1562
                    (
1456
                        $borrowernumber, $firstname,  $surname,
1563
                        $borrowernumber, $firstname,  $surname,
1457
                        $userflags,      $branchcode, $branchname,
1564
                        $branchcode, $branchname,
1458
                        $branchprinter,  $emailaddress
1565
                        $branchprinter,  $emailaddress
1459
                    ) = $sth->fetchrow if ( $sth->rows );
1566
                    ) = $sth->fetchrow if ( $sth->rows );
1460
1567
1461
                    unless ( $sth->rows ) {
1568
                    unless ( $sth->rows ) {
1462
                        $sth->execute($userid);
1569
                        $sth->execute($userid);
1463
                        (
1570
                        (
1464
                            $borrowernumber, $firstname,  $surname,       $userflags,
1571
                            $borrowernumber, $firstname,  $surname,
1465
                            $branchcode,     $branchname, $branchprinter, $emailaddress
1572
                            $branchcode,     $branchname, $branchprinter, $emailaddress
1466
                        ) = $sth->fetchrow if ( $sth->rows );
1573
                        ) = $sth->fetchrow if ( $sth->rows );
1467
                    }
1574
                    }
Lines 1495-1501 sub check_api_auth { Link Here
1495
                $session->param( 'surname',      $surname );
1602
                $session->param( 'surname',      $surname );
1496
                $session->param( 'branch',       $branchcode );
1603
                $session->param( 'branch',       $branchcode );
1497
                $session->param( 'branchname',   $branchname );
1604
                $session->param( 'branchname',   $branchname );
1498
                $session->param( 'flags',        $userflags );
1499
                $session->param( 'emailaddress', $emailaddress );
1605
                $session->param( 'emailaddress', $emailaddress );
1500
                $session->param( 'ip',           $session->remote_addr() );
1606
                $session->param( 'ip',           $session->remote_addr() );
1501
                $session->param( 'lasttime',     time() );
1607
                $session->param( 'lasttime',     time() );
Lines 1529-1534 sub check_api_auth { Link Here
1529
}
1635
}
1530
1636
1531
=head2 check_cookie_auth
1637
=head2 check_cookie_auth
1638
@DEPRECATED See Bug 7174
1532
1639
1533
  ($status, $sessionId) = check_api_auth($cookie, $userflags);
1640
  ($status, $sessionId) = check_api_auth($cookie, $userflags);
1534
1641
Lines 1556-1561 Possible return values in C<$status> are: Link Here
1556
=cut
1663
=cut
1557
1664
1558
sub check_cookie_auth {
1665
sub check_cookie_auth {
1666
    #@DEPRECATED See Bug 7174
1559
    my $cookie        = shift;
1667
    my $cookie        = shift;
1560
    my $flagsrequired = shift;
1668
    my $flagsrequired = shift;
1561
1669
Lines 1679-1690 sub get_session { Link Here
1679
    return $session;
1787
    return $session;
1680
}
1788
}
1681
1789
1790
#@DEPRECATED See Bug 7174
1682
sub checkpw {
1791
sub checkpw {
1683
    my ( $dbh, $userid, $password, $query, $type ) = @_;
1792
    my ( $dbh, $userid, $password, $query, $type ) = @_;
1684
    $type = 'opac' unless $type;
1793
    $type = 'opac' unless $type;
1685
    if ($ldap) {
1794
    if ($ldap) {
1686
        $debug and print STDERR "## checkpw - checking LDAP\n";
1795
        $debug and print STDERR "## checkpw - checking LDAP\n";
1687
        my ( $retval, $retcard, $retuserid ) = checkpw_ldap(@_);    # EXTERNAL AUTH
1796
        my ( $retval, $retcard, $retuserid ) = checkpw_ldap($userid, $password);    # EXTERNAL AUTH
1688
        return 0 if $retval == -1;                                  # Incorrect password for LDAP login attempt
1797
        return 0 if $retval == -1;                                  # Incorrect password for LDAP login attempt
1689
        ($retval) and return ( $retval, $retcard, $retuserid );
1798
        ($retval) and return ( $retval, $retcard, $retuserid );
1690
    }
1799
    }
Lines 1723-1728 sub checkpw { Link Here
1723
    return checkpw_internal(@_)
1832
    return checkpw_internal(@_)
1724
}
1833
}
1725
1834
1835
#@DEPRECATED See Bug 7174
1726
sub checkpw_internal {
1836
sub checkpw_internal {
1727
    my ( $dbh, $userid, $password ) = @_;
1837
    my ( $dbh, $userid, $password ) = @_;
1728
1838
Lines 1748-1760 sub checkpw_internal { Link Here
1748
    $sth->execute($userid);
1858
    $sth->execute($userid);
1749
    if ( $sth->rows ) {
1859
    if ( $sth->rows ) {
1750
        my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1860
        my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1751
            $surname, $branchcode, $branchname, $flags )
1861
            $surname, $branchcode, $branchname )
1752
          = $sth->fetchrow;
1862
          = $sth->fetchrow;
1753
1863
1754
        if ( checkpw_hash( $password, $stored_hash ) ) {
1864
        if ( checkpw_hash( $password, $stored_hash ) ) {
1755
1865
1756
            C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1866
            C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1757
                $firstname, $surname, $branchcode, $branchname, $flags );
1867
                $firstname, $surname, $branchcode, $branchname );
1758
            return 1, $cardnumber, $userid;
1868
            return 1, $cardnumber, $userid;
1759
        }
1869
        }
1760
    }
1870
    }
Lines 1775-1780 sub checkpw_internal { Link Here
1775
            return 1, $cardnumber, $userid;
1885
            return 1, $cardnumber, $userid;
1776
        }
1886
        }
1777
    }
1887
    }
1888
1889
    #@DEPRECATED see Bug 7174. I think the demo-user should be represented with permissions instead of a hard-coded non-borrower anomaly.
1778
    if ( $userid && $userid eq 'demo'
1890
    if ( $userid && $userid eq 'demo'
1779
        && "$password" eq 'demo'
1891
        && "$password" eq 'demo'
1780
        && C4::Context->config('demo') )
1892
        && C4::Context->config('demo') )
Lines 1787-1792 sub checkpw_internal { Link Here
1787
    return 0;
1899
    return 0;
1788
}
1900
}
1789
1901
1902
#@DEPRECATED See Bug 7174
1790
sub checkpw_hash {
1903
sub checkpw_hash {
1791
    my ( $password, $stored_hash ) = @_;
1904
    my ( $password, $stored_hash ) = @_;
1792
1905
Lines 1797-1802 sub checkpw_hash { Link Here
1797
    if ( substr( $stored_hash, 0, 2 ) eq '$2' ) {
1910
    if ( substr( $stored_hash, 0, 2 ) eq '$2' ) {
1798
        $hash = hash_password( $password, $stored_hash );
1911
        $hash = hash_password( $password, $stored_hash );
1799
    } else {
1912
    } else {
1913
        #@DEPRECATED Digest::MD5, don't use it or you will get hurt.
1800
        $hash = md5_base64($password);
1914
        $hash = md5_base64($password);
1801
    }
1915
    }
1802
    return $hash eq $stored_hash;
1916
    return $hash eq $stored_hash;
Lines 1820-1826 sub getuserflags { Link Here
1820
    my $flags  = shift;
1934
    my $flags  = shift;
1821
    my $userid = shift;
1935
    my $userid = shift;
1822
    my $dbh    = @_ ? shift : C4::Context->dbh;
1936
    my $dbh    = @_ ? shift : C4::Context->dbh;
1823
    my $userflags;
1824
    {
1937
    {
1825
        # I don't want to do this, but if someone logs in as the database
1938
        # I don't want to do this, but if someone logs in as the database
1826
        # user, it would be preferable not to spam them to death with
1939
        # user, it would be preferable not to spam them to death with
Lines 1829-1856 sub getuserflags { Link Here
1829
        $flags += 0;
1942
        $flags += 0;
1830
    }
1943
    }
1831
    return get_user_subpermissions($userid);
1944
    return get_user_subpermissions($userid);
1832
1833
    #@DEPRECATED, USE THE Koha::Auth::PermissionManager
1834
    my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1835
    $sth->execute;
1836
1837
    while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1838
        if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1839
            $userflags->{$flag} = 1;
1840
        }
1841
        else {
1842
            $userflags->{$flag} = 0;
1843
        }
1844
    }
1845
1846
    # get subpermissions and merge with top-level permissions
1847
    my $user_subperms = get_user_subpermissions($userid);
1848
    foreach my $module ( keys %$user_subperms ) {
1849
        next if $userflags->{$module} == 1;    # user already has permission for everything in this module
1850
        $userflags->{$module} = $user_subperms->{$module};
1851
    }
1852
1853
    return $userflags;
1854
}
1945
}
1855
1946
1856
=head2 get_user_subpermissions
1947
=head2 get_user_subpermissions
Lines 1952-1960 sub haspermission { Link Here
1952
2043
1953
    my $flags = getuserflags( undef, $userid );
2044
    my $flags = getuserflags( undef, $userid );
1954
    #Sanitate 1 to * because we no longer have 1's from the koha.borrowers.flags.
2045
    #Sanitate 1 to * because we no longer have 1's from the koha.borrowers.flags.
1955
    foreach my $module (%$flagsrequired) {
2046
    _changeAllPermissionsMarkerToAnyPermissionMarker($flagsrequired);
1956
        $flagsrequired->{$module} = '*' if $flagsrequired->{$module} && $flagsrequired->{$module} eq '1';
1957
    }
1958
2047
1959
    if ( $userid eq C4::Context->config('user') ) {
2048
    if ( $userid eq C4::Context->config('user') ) {
1960
2049
Lines 1989-1995 sub haspermission { Link Here
1989
    #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
2078
    #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1990
}
2079
}
1991
2080
2081
=head
2082
@DEPRECATED
2083
Sanitate 1 to * because we no longer have 1's from the koha.borrowers.flags.
2084
=cut
2085
2086
sub _changeAllPermissionsMarkerToAnyPermissionMarker {
2087
    #@DEPRECATED
2088
    my ($flagsrequired) = @_;
2089
    foreach my $module (%$flagsrequired) {
2090
        $flagsrequired->{$module} = '*' if $flagsrequired->{$module} && $flagsrequired->{$module} eq '1';
2091
    }
2092
    return $flagsrequired;
2093
}
2094
1992
sub getborrowernumber {
2095
sub getborrowernumber {
2096
    #@DEPRECATED See Bug 7174
1993
    my ($userid) = @_;
2097
    my ($userid) = @_;
1994
    my $userenv = C4::Context->userenv;
2098
    my $userenv = C4::Context->userenv;
1995
    if ( defined($userenv) && ref($userenv) eq 'HASH' && $userenv->{number} ) {
2099
    if ( defined($userenv) && ref($userenv) eq 'HASH' && $userenv->{number} ) {
(-)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/C4/Context.pm (-4 / +5 lines)
Lines 1073-1079 sub userenv { Link Here
1073
1073
1074
  C4::Context->set_userenv($usernum, $userid, $usercnum,
1074
  C4::Context->set_userenv($usernum, $userid, $usercnum,
1075
                           $userfirstname, $usersurname,
1075
                           $userfirstname, $usersurname,
1076
                           $userbranch, $branchname, $userflags,
1076
                           $userbranch, $branchname, $superlibrarian,
1077
                           $emailaddress, $branchprinter, $persona);
1077
                           $emailaddress, $branchprinter, $persona);
1078
1078
1079
Establish a hash of user environment variables.
1079
Establish a hash of user environment variables.
Lines 1085-1091 set_userenv is called in Auth.pm Link Here
1085
#'
1085
#'
1086
sub set_userenv {
1086
sub set_userenv {
1087
    shift @_;
1087
    shift @_;
1088
    my ($usernum, $userid, $usercnum, $userfirstname, $usersurname, $userbranch, $branchname, $userflags, $emailaddress, $branchprinter, $persona, $shibboleth)=
1088
    my ($usernum, $userid, $usercnum, $userfirstname, $usersurname, $userbranch, $branchname, $superlibrarian, $emailaddress, $branchprinter, $persona, $shibboleth)=
1089
    map { Encode::is_utf8( $_ ) ? $_ : Encode::decode('UTF-8', $_) } # CGI::Session doesn't handle utf-8, so we decode it here
1089
    map { Encode::is_utf8( $_ ) ? $_ : Encode::decode('UTF-8', $_) } # CGI::Session doesn't handle utf-8, so we decode it here
1090
    @_;
1090
    @_;
1091
    my $var=$context->{"activeuser"} || '';
1091
    my $var=$context->{"activeuser"} || '';
Lines 1098-1104 sub set_userenv { Link Here
1098
        #possibly a law problem
1098
        #possibly a law problem
1099
        "branch"     => $userbranch,
1099
        "branch"     => $userbranch,
1100
        "branchname" => $branchname,
1100
        "branchname" => $branchname,
1101
        "flags"      => $userflags,
1101
        "flags"      => $superlibrarian, #@DEPRECATED, use Koha::Borrower->isSuperlibrarian() instead of flags
1102
        "emailaddress"     => $emailaddress,
1102
        "emailaddress"     => $emailaddress,
1103
        "branchprinter"    => $branchprinter,
1103
        "branchprinter"    => $branchprinter,
1104
        "persona"    => $persona,
1104
        "persona"    => $persona,
Lines 1213-1218 sub tz { Link Here
1213
1213
1214
1214
1215
=head2 IsSuperLibrarian
1215
=head2 IsSuperLibrarian
1216
@DEPRECATED, use Koha::Borrower->isSuperlibrarian()
1216
1217
1217
    C4::Context->IsSuperLibrarian();
1218
    C4::Context->IsSuperLibrarian();
1218
1219
Lines 1229-1235 sub IsSuperLibrarian { Link Here
1229
        return 1;
1230
        return 1;
1230
    }
1231
    }
1231
1232
1232
    return ($userenv->{flags}//0) % 2;
1233
    return ($userenv->{flags}//0) % 2; #If flags == 1, this is true.
1233
}
1234
}
1234
1235
1235
=head2 interface
1236
=head2 interface
(-)a/Koha/Auth.pm (+227 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::RequestNormalizer;
27
use Koha::Auth::Route::Password;
28
use Koha::Auth::Route::Cookie;
29
use Koha::Auth::Route::RESTV1;
30
31
#Define Exceptions
32
use Koha::Exception::BadParameter;
33
use Koha::Exception::Logout;
34
use Koha::Exception::UnknownProgramState;
35
36
use C4::Branch;
37
38
#Define the headers, POST-parameters and cookies extracted from the various web-frameworks'
39
# request-objects and passed to the authentication system as normalized values.
40
our @authenticationHeaders = ('X-Koha-Date', 'Authorization');
41
our @authenticationPOSTparams = ('password', 'userid', 'PT', 'branch', 'logout.x', 'koha_login_context');
42
our @authenticationCookies = ('CGISESSID'); #Really we should have only one of these.
43
44
=head authenticate
45
46
@PARAM3 HASHRef of authentication directives. Supported values:
47
            'inOPAC' => 1,    #Authentication context is in OPAC
48
            'inREST' => 'v1', #Authentication context is in REST API V1
49
            'inSC'   => 1,    #Authentication context is in the staff client
50
            'authnotrequired' => 1, #Disregard all Koha::Exception::LoginFailed||NoPermission-exceptions,
51
                                    #and authenticate as an anonymous user if normal authentication
52
                                    #fails.
53
@THROWS Koha::Exception::VersionMismatch
54
        Koha::Exception::BadSystemPreference
55
        Koha::Exception::BadParameter
56
        Koha::Exception::ServiceTemporarilyUnavailable
57
        Koha::Exception::LoginFailed
58
        Koha::Exception::NoPermission
59
        Koha::Exception::Logout, catch this and redirect the request to the logout page.
60
=cut
61
62
sub authenticate {
63
    my ($controller, $permissions, $authParams) = @_;
64
    my $rae = _authenticate_validateAndNormalizeParameters(@_); #Get the normalized request authentication elements
65
66
    my $borrower; #Each authentication route returns a Koha::Borrower-object on success. We use this to generate the Context()
67
68
    ##Select the Authentication route.
69
    ##Routes are introduced in priority order, and if one matches, the other routes are ignored.
70
    try {
71
        #0. Logout
72
        if ($rae->{postParams}->{'logout.x'}) {
73
            clearUserEnvironment($rae, $authParams);
74
            Koha::Exception::Logout->throw(error => "User logged out. Please redirect me!");
75
        }
76
        #1. Check for password authentication, including LDAP.
77
        elsif ($rae->{postParams}->{koha_login_context} && $rae->{postParams}->{userid} && $rae->{postParams}->{password}) {
78
            $borrower = Koha::Auth::Route::Password::challenge($rae, $permissions, $authParams);
79
        }
80
        #2. Check for REST's signature-based authentication.
81
        #elsif ($rae->{headers}->{'Authorization'} && $rae->{headers}->{'Authorization'} =~ /Koha/) {
82
        elsif ($rae->{headers}->{'Authorization'}) {
83
            $borrower = Koha::Auth::Route::RESTV1::challenge($rae, $permissions, $authParams);
84
        }
85
        #3. Check for the cookie. If cookies go stale, they block all subsequent authentication methods, so keep it down on this list.
86
        elsif ($rae->{cookies}->{CGISESSID}) {
87
            $borrower = Koha::Auth::Route::Cookie::challenge($rae, $permissions, $authParams);
88
        }
89
        else { #HTTP CAS ticket or shibboleth or Persona not implemented
90
            #We don't know how to authenticate, or there is no authentication attempt.
91
            Koha::Exception::LoginFailed->throw(error => "Koha doesn't understand your authentication protocol.");
92
        }
93
    } catch {
94
        if (blessed($_)) {
95
            if ($_->isa('Koha::Exception::LoginFailed') || $_->isa('Koha::Exception::NoPermission')) {
96
                if ($authParams->{authnotrequired}) { #We failed to login, but we can continue anonymously.
97
                    $borrower = Koha::Borrower->new();
98
                }
99
                else {
100
                    $_->rethrow(); #Anonymous login not allowed this time
101
                }
102
            }
103
            else {
104
                die $_; #Propagate other errors to the calling Controller to redirect as it wants.
105
            }
106
        }
107
        else {
108
            die $_; #Not a Koha::Exception-object
109
        }
110
    };
111
112
    my $session = setUserEnvironment($controller, $rae, $borrower, $authParams);
113
    my $cookie = Koha::Auth::RequestNormalizer::getSessionCookie($controller, $session);
114
115
    return ($borrower, $cookie);
116
}
117
118
=head _authenticate_validateAndNormalizeParameters
119
120
@PARAM1 CGI- or Mojolicious::Controller-object, this is used to identify which web framework to use.
121
@PARAM2 HASHRef or undef, Permissions HASH telling which Koha permissions the user must have, to access the resource.
122
@PARAM3 HASHRef or undef, Special authentication parameters, see authenticate()
123
@THROWS Koha::Exception::BadParameter, if validating parameters fails.
124
=cut
125
126
sub _authenticate_validateAndNormalizeParameters {
127
    my ($controller, $permissions, $authParams) = @_;
128
129
    #Validate $controller.
130
    my $requestAuthElements;
131
    if (blessed($controller) && $controller->isa('CGI')) {
132
        $requestAuthElements = Koha::Auth::RequestNormalizer::normalizeCGI($controller, \@authenticationHeaders, \@authenticationPOSTparams, \@authenticationCookies);
133
    }
134
    elsif (blessed($controller) && $controller->isa('Mojolicious::Controller')) {
135
        $requestAuthElements = Koha::Auth::RequestNormalizer::normalizeMojolicious($controller, \@authenticationHeaders, \@authenticationPOSTparams, \@authenticationCookies);
136
    }
137
    else {
138
        Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The first parameter MUST be either a 'CGI'-object or a 'Mojolicious::Controller'-object");
139
    }
140
    #Validate $permissions
141
    unless (not($permissions) || (ref $permissions eq 'HASH')) {
142
        Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The second parameter MUST be 'undef' or a HASHRef of Koha permissions. See C4::Auth::haspermission().");
143
    }
144
    #Validate $authParams
145
    unless (not($authParams) || (ref $authParams eq 'HASH')) {
146
        Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The third parameter MUST be 'undef' or a HASHRef.");
147
    }
148
149
    return $requestAuthElements;
150
}
151
152
=head setUserEnvironment
153
Set the C4::Context::user_env() and CGI::Session.
154
155
Any idea why there is both the CGI::Session and C4::Context::usernenv??
156
=cut
157
158
sub setUserEnvironment {
159
    my ($controller, $rae, $borrower, $authParams) = @_;
160
161
    my $session = C4::Auth::get_session( $rae->{cookies}->{CGISESSID} || '' );
162
    C4::Context->_new_userenv( $session->id );
163
164
    _determineUserBranch($rae, $borrower, $authParams, $session);
165
166
    #Then start setting remaining session parameters
167
    $session->param( 'number',       $borrower->borrowernumber );
168
    $session->param( 'id',           $borrower->userid );
169
    $session->param( 'cardnumber',   $borrower->cardnumber );
170
    $session->param( 'firstname',    $borrower->firstname );
171
    $session->param( 'surname',      $borrower->surname );
172
    $session->param( 'emailaddress', $borrower->email );
173
    $session->param( 'ip',           $session->remote_addr() );
174
    $session->param( 'lasttime',     time() );
175
176
    #Finally configure the userenv.
177
    C4::Context->set_userenv(
178
        $session->param('number'),       $session->param('id'),
179
        $session->param('cardnumber'),   $session->param('firstname'),
180
        $session->param('surname'),      $session->param('branch'),
181
        $session->param('branchname'),   undef,
182
        $session->param('emailaddress'), $session->param('branchprinter'),
183
        $session->param('persona'),      $session->param('shibboleth')
184
    );
185
186
    return $session;
187
}
188
189
sub _determineUserBranch {
190
    my ($rae, $borrower, $authParams, $session) = @_;
191
192
    my ($branchcode, $branchname);
193
    if ($rae->{postParams}->{branch}) {
194
        #We are instructed to change the active branch
195
        $branchcode = $rae->{postParams}->{branch};
196
    }
197
    elsif ($session->param('branch')) {
198
        ##Branch is already set so no reason to change it.
199
        return;
200
    }
201
    elsif ($borrower->branchcode) {
202
        #Default to the borrower's branch
203
        $branchcode = $borrower->branchcode;
204
    }
205
    else {
206
        #No borrower branch? This must be the superuser.
207
        $branchcode = 'NO_LIBRARY_SET';
208
        $branchname = 'NO_LIBRARY_SET';
209
    }
210
    $session->param( 'branch',     $branchcode );
211
    $session->param( 'branchname', ($branchname || C4::Branch::GetBranchName($branchcode)));
212
}
213
214
=head clearUserEnvironment
215
216
Removes all active authentications
217
=cut
218
219
sub clearUserEnvironment {
220
    my ($rae, $authParams) = @_;
221
222
    my $session = C4::Auth::get_session( $rae->{cookies}->{CGISESSID} );
223
    $session->delete();
224
    $session->flush;
225
    C4::Context->_unset_userenv( $rae->{cookies}->{CGISESSID} );
226
}
227
1;
(-)a/Koha/Auth/Component.pm (+340 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 Digest::SHA qw(hmac_sha256_hex);
23
use DateTime::Format::HTTP;
24
25
use Koha qw(version);
26
use Koha::Borrowers;
27
use Koha::AuthUtils;
28
use Koha::Auth::Component::Password;
29
use Koha::Auth::PermissionManager;
30
use C4::Auth;
31
32
use Koha::Exception::VersionMismatch;
33
use Koha::Exception::BadSystemPreference;
34
use Koha::Exception::ServiceTemporarilyUnavailable;
35
use Koha::Exception::LoginFailed;
36
37
=head1 NAME Koha::Auth::Component
38
39
=head2 SYNOPSIS
40
41
In this package we define the authentication steps we can use to define
42
authentication path behaviour.
43
44
=head2 USAGE
45
46
    use Scalar::Util qw(blessed);
47
    try {
48
        ...
49
        Koha::Auth::Component::checkVersion();
50
        Koha::Auth::Component::checkOPACMaintenance();
51
        ...
52
    } catch {
53
        if (blessed($_)) {
54
            if ($_->isa('Koha::Exception::VersionMismatch')) {
55
                ##handle exception
56
            }
57
            elsif ($_->isa('Koha::Exception::AnotherKindOfException')) {
58
                ...
59
            }
60
            ...
61
            else {
62
                warn "Unknown exception class ".ref($_)."\n";
63
                die $_; #Unhandled exception case
64
            }
65
        }
66
        else {
67
            die $_; #Not a Koha::Exception-object
68
        }
69
    };
70
71
=cut
72
73
=head checkVersion
74
STATIC
75
76
    Koha::Auth::Component::checkVersion();
77
78
Checks if the DB version is valid.
79
80
@THROWS Koha::Exception::VersionMismatch, if versions do not match
81
@THROWS Koha::Exception::BadSystemPreference, if "Version"-syspref is not set.
82
                        This probably means that Koha has not been installed yet.
83
=cut
84
85
sub checkVersion {
86
    my $versionSyspref = C4::Context->preference('Version');
87
    unless ( $versionSyspref ) {
88
        Koha::Exception::BadSystemPreference->throw(error => "No Koha 'Version'-system preference defined. Koha needs to be installed.");
89
    }
90
91
    my $kohaversion = Koha::version();
92
    # remove the 3 last . to have a Perl number
93
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
94
    if ( $versionSyspref < $kohaversion ) {
95
        Koha::Exception::VersionMismatch->throw(error => "Database update needed. Database is 'v$versionSyspref' and Koha is 'v$kohaversion'");
96
    }
97
}
98
99
=head checkOPACMaintenance
100
STATIC
101
102
    Koha::Auth::Component::checkOPACMaintenance();
103
104
Checks if OPAC is under maintenance.
105
106
@THROWS Koha::Exception::ServiceTemporarilyUnavailable
107
=cut
108
109
sub checkOPACMaintenance {
110
    if ( C4::Context->preference('OpacMaintenance') ) {
111
        Koha::Exception::ServiceTemporarilyUnavailable->throw(error => 'OPAC is under maintenance');
112
    }
113
}
114
115
=head checkPassword
116
STATIC
117
118
    Koha::Auth::Component::checkPassword();
119
120
@RETURN Koha::Borrower-object if check succeedes, otherwise throws exceptions.
121
@THROWS Koha::Exception::LoginFailed from Koha::AuthUtils password checks.
122
=cut
123
124
sub checkPassword {
125
    my ($userid, $password) = @_;
126
127
    my $borrower;
128
    if (C4::Context->config('useldapserver')) {
129
        $borrower = Koha::Auth::Component::Password::checkLDAPPassword($userid, $password);
130
        return $borrower if $borrower;
131
    }
132
    if (C4::Context->preference('casAuthentication')) {
133
        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 :)");
134
    }
135
    if (C4::Context->config('useshibboleth')) {
136
        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 :)");
137
    }
138
139
    return Koha::Auth::Component::Password::checkKohaPassword($userid, $password);
140
}
141
142
=head checkPermissions
143
STATIC
144
145
    Koha::Auth::Component::checkPermissions($borrower, $permissionsRequired);
146
147
@THROWS Koha::Exception::NoPermission with the missing permission if permissions
148
                are inadequate
149
=cut
150
151
sub checkPermissions {
152
    my ($borrower, $permissionsRequired) = @_;
153
154
    my $permissionManager = Koha::Auth::PermissionManager->new();
155
    $permissionManager->hasPermissions($borrower, $permissionsRequired);
156
}
157
158
=head checkCookie
159
STATIC
160
161
    Koha::Auth::Component::checkCookie($cookieValue);
162
163
Checks if the given authentication cookie value matches a session, and checks if
164
the session is still active.
165
@PARAM1 String, hashed session key identifying a session in koha.sessions
166
@RETURNS Koha::Borrower matching the verified and active session
167
@THROWS Koha::Exception::LoginFailed, if no session is found,
168
                                      if the session has expired,
169
                                      if the session IP address changes,
170
                                      if no borrower was found for the session
171
=cut
172
173
sub checkCookie {
174
    my ($cookie) = @_;
175
176
    my $session = C4::Auth::get_session($cookie);
177
    Koha::Exception::LoginFailed->throw(error => "No session matching the given session identifier '$session'.") unless $session;
178
179
    # See if the given session is timed out
180
    if ( ($session->param('lasttime') || 0) < (time()- C4::Auth::_timeout_syspref()) ) {
181
        $session->delete();
182
        $session->flush;
183
        C4::Context::_unset_userenv($cookie);
184
        Koha::Exception::LoginFailed->throw(error => "Session expired, please login again.");
185
    }
186
    # Check if we still access using the same IP than when the session was initialized.
187
    elsif ( C4::Context->preference('SessionRestrictionByIP') && $session->param('ip') ne $ENV{'REMOTE_ADDR'} ) {
188
        $session->delete();
189
        $session->flush;
190
        C4::Context->_unset_userenv($cookie);
191
        Koha::Exception::LoginFailed->throw(error => "Session's client address changed, please login again.");
192
    }
193
194
    #Get the Borrower-object
195
    my $userid   = $session->param('id');
196
    my $borrower = Koha::AuthUtils::checkKohaSuperuserFromUserid($userid);
197
    $borrower = Koha::Borrowers->find({userid => $userid}) unless $borrower;
198
    Koha::Exception::LoginFailed->throw(error => "Cookie authentication succeeded, but no borrower found with userid '$userid'.")
199
            unless $borrower;
200
201
    $session->param( 'lasttime', time() );
202
    return $borrower;
203
}
204
205
=head checkIndependentBranchesAutolocation
206
207
If sysprefs 'IndependentBranches' and 'Autolocation' are active, checks if the user
208
is in the correct network region to login.
209
@PARAM1 String, branchcode of the branch the current user is authenticating in to.
210
@THROWS Koha::Exception::LoginFailed, if the user is in the wrong network segment.
211
=cut
212
213
sub checkIndependentBranchesAutolocation {
214
    my ($currentBranchcode) = @_;
215
216
    if ( $currentBranchcode && C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation') ) {
217
        my $ip = $ENV{'REMOTE_ADDR'};
218
219
        my $branches = GetBranches();
220
        # we have to check they are coming from the right ip range
221
        my $domain = $branches->{$currentBranchcode}->{'branchip'};
222
        if ( $ip !~ /^$domain/ ) {
223
            Koha::Exception::LoginFailed->throw(error => "Branch '$currentBranchcode' is inaccessible from this network.");
224
        }
225
    }
226
}
227
228
=head checkRESTV1
229
230
    my $borrower = Koha::Auth::Component::checkRESTV1();
231
232
For authentication to succeed, the client have to send 2 HTTP
233
headers:
234
 - X-Koha-Date: the standard HTTP Date header complying to RFC 1123, simply wrapped to X-Koha-Date,
235
                since the w3-specification forbids setting the Date-header from javascript.
236
 - Authorization: the standard HTTP Authorization header, see below for how it is constructed.
237
238
=head2 HTTP Request example
239
240
GET /api/v1/borrowers/12 HTTP/1.1
241
Host: api.yourkohadomain.fi
242
X-Koha-Date: Mon, 26 Mar 2007 19:37:58 +0000
243
Authorization: Koha admin69:frJIUN8DYpKDtOLCwo//yllqDzg=
244
245
=head2 Constructing the Authorization header
246
247
-You brand the authorization header with "Koha"
248
-Then you give the userid/cardnumber of the user authenticating.
249
-Then the hashed signature.
250
251
The signature is a HMAC-SHA256 hash of several elements of the request,
252
separated by spaces:
253
 - HTTP method (uppercase)
254
 - userid/cardnumber
255
 - X-Koha-Date-header
256
Signed with the Borrowers API key
257
258
The server then tries to rebuild the signature with each of the user's API keys.
259
If one matches the received signature, then authentication is almost OK.
260
261
To avoid requests to be replayed, the last request's X-Koha-Date-header is stored
262
in database and the authentication succeeds only if the stored Date
263
is lesser than the X-Koha-Date-header.
264
265
=head2 Constructing the signature example
266
267
Signature = HMAC-SHA256-HEX("HTTPS" + " " +
268
                            "/api/v1/borrowers/12?howdoyoudo=voodoo" + " " +
269
                            "admin69" + " " +
270
                            "760818212" + " " +
271
                            "frJIUN8DYpKDtOLCwo//yllqDzg="
272
                           );
273
274
=head
275
276
@PARAM1 HASHRef of Header name => values
277
@PARAM2 String, upper case request method name, eg. HTTP or HTTPS
278
@PARAM3 String the request uri
279
@RETURNS Koha::Borrower if authentication succeeded.
280
@THROWS Koha::Exception::LoginFailed, if API key signature verification failed
281
@THROWS Koha::Exception::BadParameter
282
@THROWS Koha::Exception::UnknownObject, if we cannot find a Borrower with the given input.
283
=cut
284
285
sub checkRESTV1 {
286
    my ($headers, $method, $uri) = @_;
287
288
    my $req_dt;
289
    eval {
290
        $req_dt = DateTime::Format::HTTP->parse_datetime( $headers->{'X-Koha-Date'} ); #Returns DateTime
291
    };
292
    my $authorizationHeader = $headers->{'Authorization'};
293
    my ($req_username, $req_signature);
294
    if ($authorizationHeader =~ /^Koha (\w+?):(\w+)$/) {
295
        $req_username = $1;
296
        $req_signature = $2;
297
    }
298
    else {
299
        Koha::Exception::BadParameter->throw(error => "Authorization HTTP-header is not well formed. It needs to be of format 'Authorization: Koha userid:signature'");
300
    }
301
    unless ($req_dt) {
302
        Koha::Exception::BadParameter->throw(error => "X-Koha-Date HTTP-header is not well formed. It needs to be of RFC 1123 -date format, eg. 'X-Koha-Date: Wed, 09 Feb 1994 22:23:32 +0200'");
303
    }
304
305
    my $borrower = Koha::Borrowers::castToBorrower($req_username);
306
307
    my @apikeys = Koha::ApiKeys->search({
308
        borrowernumber => $borrower->borrowernumber,
309
        active => 1,
310
    });
311
    Koha::Exception::LoginFailed->throw(error => "User has no API keys. Please add one using the Staff interface or OPAC.") unless @apikeys;
312
313
    my $message = join(' ', uc($method), $req_username, $headers->{'X-Koha-Date'});
314
    my $signature = '';
315
    my $matchingApiKey;
316
    foreach my $apikey (@apikeys) {
317
        $signature = Digest::SHA::hmac_sha256_hex($message, $apikey->api_key);
318
319
        if ($signature eq $req_signature) {
320
            $matchingApiKey = $apikey;
321
            last();
322
        }
323
    }
324
325
    unless ($signature eq $req_signature) {
326
        Koha::Exception::LoginFailed->throw(error => "API key authentication failed");
327
    }
328
329
    unless ($matchingApiKey->last_request_time < $req_dt->epoch()) {
330
        DateTime::Format::HTTP->format_datetime($req_dt);
331
        Koha::Exception::BadParameter->throw(error => "X-Koha-Date HTTP-header is stale, expected later date than '".DateTime::Format::HTTP->format_datetime($req_dt)."'");
332
    }
333
334
    $matchingApiKey->set({last_request_time => $req_dt->epoch()});
335
    $matchingApiKey->store();
336
337
    return $borrower;
338
}
339
340
1;
(-)a/Koha/Auth/Component/Password.pm (+91 lines)
Line 0 Link Here
1
package Koha::Auth::Component::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::AuthUtils;
23
24
our @usernameAliasColumns = ('userid', 'cardnumber'); #Possible columns to treat as the username when authenticating. Must be UNIQUE in DB.
25
26
=head NAME Koha::Auth::Component::Password
27
28
=head SYNOPSIS
29
30
This module implements the more specific behaviour of the password authentication component.
31
32
=head checkKohaPassword
33
34
    my $borrower = Koha::AuthUtils::checkKohaPassword($userid, $password);
35
36
Checks if the given username and password match anybody in the Koha DB
37
@PARAM1 String, user identifier, either the koha.borrowers.userid, or koha.borrowers.cardnumber
38
@PARAM2 String, clear text password from the authenticating user
39
@RETURN Koha::Borrower, if login succeeded.
40
                Sets Koha::Borrower->isSuperuser() if the user is a superuser.
41
@THROWS Koha::Exception::LoginFailed, if no matching password was found for all username aliases in Koha.
42
=cut
43
44
sub checkKohaPassword {
45
    my ($userid, $password) = @_;
46
    my $borrower; #Find the borrower to return
47
48
    $borrower = Koha::AuthUtils::checkKohaSuperuser($userid, $password);
49
    return $borrower if $borrower;
50
51
    my $usernameFound = 0; #Report to the user if userid/barcode was found, even if the login failed.
52
    #Check for each username alias if we can confirm a login with that.
53
    for my $unameAlias (@usernameAliasColumns) {
54
        my $borrower = Koha::Borrowers->find({$unameAlias => $userid});
55
        if ( $borrower ) {
56
            $usernameFound = 1;
57
            return $borrower if ( Koha::AuthUtils::checkHash( $password, $borrower->password ) );
58
        }
59
    }
60
61
    Koha::Exception::LoginFailed->throw(error => "Password authentication failed for the given ".( ($usernameFound) ? "password" : "username and password").".");
62
}
63
64
=head checkLDAPPassword
65
66
Checks if the given username and password match anybody in the LDAP service
67
@PARAM1 String, user identifier
68
@PARAM2 String, clear text password from the authenticating user
69
@RETURN Koha::Borrower, or
70
            undef if we couldn't reliably contact the LDAP server so we should
71
            fallback to local Koha Password authentication.
72
@THROWS Koha::Exception::LoginFailed, if LDAP login failed
73
=cut
74
75
sub checkLDAPPassword {
76
    my ($userid, $password) = @_;
77
78
    #Lazy load dependencies because somebody might never need them.
79
    require C4::Auth_with_ldap;
80
81
    my ($retval, $cardnumber, $local_userid) = C4::Auth_with_ldap::checkpw_ldap($userid, $password);    # EXTERNAL AUTH
82
    if ($retval == -1) {
83
        Koha::Exception::LoginFailed->throw(error => "LDAP authentication failed for the given username and password");
84
    }
85
86
    if ($retval) {
87
        my $borrower = Koha::Borrower->find({userid => $local_userid});
88
        return $borrower;
89
    }
90
    return undef;
91
}
(-)a/Koha/Auth/RequestNormalizer.pm (+157 lines)
Line 0 Link Here
1
package Koha::Auth::RequestNormalizer;
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 Scalar::Util qw(blessed);
23
24
25
=head normalizeCGI
26
27
Takes a CGI-object and finds the authentication markers from it.
28
@PARAM1 CGI-object.
29
@PARAM2 ARRAYRef, authentication headers that should be extracted for authentication
30
@PARAM3 ARRAYRef, authentication POST parameters that should be extracted for authentication
31
@PARAM4 ARRAYRef, authentication cookies that should be extracted for authentication
32
@RETURNS List of : HASHRef of headers required for authentication, or undef
33
                   HASHRef of POST parameters required for authentication, or undef
34
                   HASHRef of the authenticaton cookie name => value, or undef
35
=cut
36
37
sub normalizeCGI {
38
    my ($controller, $authenticationHeaders, $authenticationPOSTparams, $authenticationCookies) = @_;
39
40
    my ($headers, $postParams, $cookies) = ({}, {}, {});
41
    foreach my $authHeader (@$authenticationHeaders) {
42
        if (my $val = $controller->http($authHeader)) {
43
            $headers->{$authHeader} = $val;
44
        }
45
    }
46
    foreach my $authParam (@$authenticationPOSTparams) {
47
        if (my $val = $controller->param($authParam)) {
48
            $postParams->{$authParam} = $val;
49
        }
50
    }
51
    foreach my $authCookie (@$authenticationCookies) {
52
        if (my $val = $controller->cookie($authCookie)) {
53
            $cookies->{$authCookie} = $val;
54
        }
55
    }
56
    my $method = $1 if ($ENV{SERVER_PROTOCOL} =~ /^(.+?)\//);
57
    my $requestAuthElements = { #Collect the authentication elements here.
58
        headers => $headers,
59
        postParams => $postParams,
60
        cookies => $cookies,
61
        method => $method,
62
        url => $ENV{REQUEST_URI},
63
    };
64
    return $requestAuthElements;
65
}
66
67
=head normalizeMojolicious
68
69
Takes a Mojolicious::Controller-object and finds the authentication markers from it.
70
@PARAM1 Mojolicious::Controller-object.
71
@PARAM2-4 See normalizeCGI()
72
@RETURNS HASHRef of the request's authentication elements marked for extraction, eg:
73
        {
74
            headers => { X-Koha-Signature => '32rFrFw3iojsev34AS',
75
                         X-Koha-Username => 'pavlov'},
76
            POSTparams => { password => '1234',
77
                            userid => 'pavlov'},
78
            cookies => { CGISESSID => '233FADFEV3as1asS' },
79
            method => 'https',
80
            url => '/borrower/12/holds'
81
        }
82
=cut
83
84
sub normalizeMojolicious {
85
    my ($controller, $authenticationHeaders, $authenticationPOSTparams, $authenticationCookies) = @_;
86
87
    my $request = $controller->req();
88
    my ($headers, $postParams, $cookies) = ({}, {}, {});
89
    my $headersHash = $request->headers()->to_hash();
90
    foreach my $authHeader (@$authenticationHeaders) {
91
        if (my $val = $headersHash->{$authHeader}) {
92
            $headers->{$authHeader} = $val;
93
        }
94
    }
95
    foreach my $authParam (@$authenticationPOSTparams) {
96
        if (my $val = $request->param($authParam)) {
97
            $postParams->{$authParam} = $val;
98
        }
99
    }
100
101
    my $requestCookies = $request->cookies;
102
    if (scalar(@$requestCookies)) {
103
        foreach my $authCookieName (@$authenticationCookies) {
104
            foreach my $requestCookie (@$requestCookies) {
105
                if ($authCookieName eq $requestCookie->name) {
106
                    $cookies->{$authCookieName} = $requestCookie->value;
107
                }
108
            }
109
        }
110
    }
111
112
    my $requestAuthElements = { #Collect the authentication elements here.
113
        headers => $headers,
114
        postParams => $postParams,
115
        cookies => $cookies,
116
        method => $controller->req->method,
117
        url => '/' . $controller->req->url->path_query,
118
    };
119
    return $requestAuthElements;
120
}
121
122
=head getSessionCookie
123
124
@PARAM1 CGI- or Mojolicious::Controller-object, this is used to identify which web framework to use.
125
@PARAM2 CGI::Session.
126
@RETURNS a Mojolicious cookie or a CGI::Cookie.
127
=cut
128
129
sub getSessionCookie {
130
    my ($controller, $session) = @_;
131
132
    my $cookie = {
133
            name     => 'CGISESSID',
134
            value    => $session->id,
135
    };
136
    my $cookieOk;
137
138
    if (blessed($controller) && $controller->isa('CGI')) {
139
        $cookie->{HttpOnly} = 1;
140
        $cookieOk = $controller->cookie( $cookie );
141
    }
142
    elsif (blessed($controller) && $controller->isa('Mojolicious::Controller')) {
143
        $controller->res->cookies($cookie);
144
        foreach my $c (@{$controller->res->cookies}) {
145
            if ($c->name eq 'CGISESSID') {
146
                $cookieOk = $c;
147
                last;
148
            }
149
        }
150
    }
151
    unless ($cookieOk) {
152
        Koha::Exception::UnknownProgramState->throw(error => __PACKAGE__."::getSessionCookie():> Unable to get a proper cookie?");
153
    }
154
    return $cookieOk;
155
}
156
157
1;
(-)a/Koha/Auth/Route.pm (+69 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($requestAuthElements, $permissionsRequired, $routeParams);
33
    }
34
35
=head INPUT
36
37
Each Route gets three parameters:
38
    $requestAuthElements, HASHRef of HASHRefs:
39
        headers =>      HASHRef of HTTP Headers matching the @authenticationHeaders-package
40
                        variable in Koha::Auth,
41
                        Eg. { 'X-Koha-Signature' => "23in4ow2gas2opcnpa", ... }
42
        postParams =>   HASHRef of HTTP POST parameters matching the
43
                        @authenticationPOSTparams-package variable in Koha::Auth,
44
                        Eg. { password => '1234', 'userid' => 'admin'}
45
        cookies =>      HASHRef of HTTP Cookies matching the
46
                        @authenticationPOSTparams-package variable in Koha::Auth,
47
                        EG. { CGISESSID => '9821rj1kn3tr9ff2of2ln1' }
48
    $permissionsRequired:
49
                        HASHRef of Koha permissions.
50
                        See C4::Auth::haspermission() for example.
51
    $routeParams:       HASHRef of special Route-related data
52
                        {inOPAC => 1, authnotrequired => 0, ...}
53
54
=head OUTPUT
55
56
Each route must return a Koha::Borrower-object representing the authenticated user.
57
Even if the login succeeds with a superuser or similar virtual user, like
58
anonymous login, a mock Borrower-object must be returned.
59
If the login fails, each route must throw Koha::Exceptions to notify the cause
60
of the failure.
61
62
=head ROUTE STRUCTURE
63
64
Each route consists of Koha::Auth::Component-subroutine calls to test for various
65
authentication challenges.
66
67
=cut
68
69
1;
(-)a/Koha/Auth/Route/Cookie.pm (+41 lines)
Line 0 Link Here
1
package Koha::Auth::Route::Cookie;
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
use base qw(Koha::Auth::Route);
25
26
=head challenge
27
See Koha::Auth::Route, for usage documentation.
28
@THROWS Koha::Exceptions from authentication components.
29
=cut
30
31
sub challenge {
32
    my ($rae, $permissionsRequired, $routeParams) = @_;
33
34
    Koha::Auth::Component::checkOPACMaintenance() if $routeParams->{inOPAC};
35
    Koha::Auth::Component::checkVersion();
36
    my $borrower = Koha::Auth::Component::checkCookie($rae->{cookies}->{CGISESSID});
37
    Koha::Auth::Component::checkPermissions($borrower, $permissionsRequired) if $permissionsRequired;
38
    return $borrower;
39
}
40
41
1;
(-)a/Koha/Auth/Route/Password.pm (+42 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
use base qw(Koha::Auth::Route);
25
26
=head challenge
27
See Koha::Auth::Route, for usage documentation.
28
@THROWS Koha::Exceptions from authentication components.
29
=cut
30
31
sub challenge {
32
    my ($rae, $permissionsRequired, $routeParams) = @_;
33
34
    Koha::Auth::Component::checkOPACMaintenance() if $routeParams->{inOPAC};
35
    Koha::Auth::Component::checkVersion();
36
    Koha::Auth::Component::checkIndependentBranchesAutolocation($routeParams->{branch});
37
    my $borrower = Koha::Auth::Component::checkPassword($rae->{postParams}->{userid}, $rae->{postParams}->{password});
38
    Koha::Auth::Component::checkPermissions($borrower, $permissionsRequired) if $permissionsRequired;
39
    return $borrower;
40
}
41
42
1;
(-)a/Koha/Auth/Route/RESTV1.pm (+41 lines)
Line 0 Link Here
1
package Koha::Auth::Route::RESTV1;
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
use base qw(Koha::Auth::Route);
25
26
=head challenge
27
See Koha::Auth::Route, for usage documentation.
28
@THROWS Koha::Exceptions from authentication components.
29
=cut
30
31
sub challenge {
32
    my ($rae, $permissionsRequired, $routeParams) = @_;
33
34
    #Koha::Auth::Component::checkRESTMaintenance() if $routeParams->{inREST}; #NOT IMPLEMENTED YET
35
    Koha::Auth::Component::checkVersion();
36
    my $borrower = Koha::Auth::Component::checkRESTV1($rae->{headers}, $rae->{method}, $rae->{url});
37
    Koha::Auth::Component::checkPermissions($borrower, $permissionsRequired) if $permissionsRequired;
38
    return $borrower;
39
}
40
41
1;
(-)a/Koha/AuthUtils.pm (-1 / +64 lines)
Lines 24-29 use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt Link Here
24
24
25
use Koha::Borrower;
25
use Koha::Borrower;
26
26
27
use Koha::Exception::LoginFailed;
28
27
use base 'Exporter';
29
use base 'Exporter';
28
30
29
our $VERSION = '1.01';
31
our $VERSION = '1.01';
Lines 136-141 sub generate_salt { Link Here
136
    return $string;
138
    return $string;
137
}
139
}
138
140
141
=head checkHash
142
143
    my $passwordOk = Koha::AuthUtils::checkHash($password1, $password2)
144
145
Checks if a clear-text String/password matches the given hash when
146
MD5 or Bcrypt hashing algorith is applied to it.
147
148
Bcrypt is applied if @PARAM2 starts with '$2'
149
MD5 otherwise
150
151
@PARAM1 String, clear text passsword or any other String
152
@PARAM2 String, hashed text password or any other String.
153
@RETURN Boolean, 1 if given parameters match
154
               , 0 if not
155
=cut
156
157
sub checkHash {
158
    my ( $password, $stored_hash ) = @_;
159
160
    $password = Encode::encode( 'UTF-8', $password )
161
            if Encode::is_utf8($password);
162
163
    return if $stored_hash eq '!';
164
165
    my $hash;
166
    if ( substr( $stored_hash, 0, 2 ) eq '$2' ) {
167
        $hash = hash_password( $password, $stored_hash );
168
    } else {
169
        #@DEPRECATED Digest::MD5, don't use it or you will get hurt.
170
        require Digest::MD5;
171
        $hash = Digest::MD5::md5_base64($password);
172
    }
173
    return $hash eq $stored_hash;
174
}
175
176
=head checkKohaSuperuser
177
178
    my $borrower = Koha::AuthUtils::checkKohaSuperuser($userid, $password);
179
180
Check if the userid and password match the ones in the $KOHA_CONF
181
@PARAM1 String, user identifier, either the koha.borrowers.userid, or koha.borrowers.cardnumber
182
@PARAM2 String, clear text password from the authenticating user
183
@RETURNS Koha::Borrower branded as superuser with ->isSuperuser()
184
         or undef if user logging in is not a superuser.
185
@THROWS Koha::Exception::LoginFailed if user identifier matches, but password doesn't
186
=cut
187
188
sub checkKohaSuperuser {
189
    my ($userid, $password) = @_;
190
191
    if ( $userid && $userid eq C4::Context->config('user') ) {
192
        if ( $password && $password eq C4::Context->config('pass') ) {
193
            return _createTemporarySuperuser();
194
        }
195
        else {
196
            Koha::Exception::LoginFailed->throw(error => "Password authentication failed");
197
        }
198
    }
199
}
200
139
=head checkKohaSuperuserFromUserid
201
=head checkKohaSuperuserFromUserid
140
See checkKohaSuperuser(), with only the "user identifier"-@PARAM.
202
See checkKohaSuperuser(), with only the "user identifier"-@PARAM.
141
@THROWS nothing.
203
@THROWS nothing.
Lines 153-165 sub checkKohaSuperuserFromUserid { Link Here
153
215
154
Create a temporary superuser which should be instantiated only to the environment
216
Create a temporary superuser which should be instantiated only to the environment
155
and then discarded. So do not ->store() it!
217
and then discarded. So do not ->store() it!
156
@RETURN Koha::Borrower
218
@RETURN Koha::Borrower, stamped as superuser.
157
=cut
219
=cut
158
220
159
sub _createTemporarySuperuser {
221
sub _createTemporarySuperuser {
160
    my $borrower = Koha::Borrower->new();
222
    my $borrower = Koha::Borrower->new();
161
223
162
    my $superuserName = C4::Context->config('user');
224
    my $superuserName = C4::Context->config('user');
225
    $borrower->isSuperuser(1);
163
    $borrower->set({borrowernumber => 0,
226
    $borrower->set({borrowernumber => 0,
164
                       userid     => $superuserName,
227
                       userid     => $superuserName,
165
                       cardnumber => $superuserName,
228
                       cardnumber => $superuserName,
(-)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/koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt (-1 / +3 lines)
Lines 43-49 Link Here
43
<form action="[% url %]" method="post" name="loginform" id="loginform">
43
<form action="[% url %]" method="post" name="loginform" id="loginform">
44
    <input type="hidden" name="koha_login_context" value="intranet" />
44
    <input type="hidden" name="koha_login_context" value="intranet" />
45
[% FOREACH INPUT IN INPUTS %]
45
[% FOREACH INPUT IN INPUTS %]
46
    <input type="hidden" name="[% INPUT.name |html %]" value="[% INPUT.value |html %]" />
46
	[% UNLESS INPUT.name == 'logout.x' #No reason to send the logout-signal again %]
47
        <input type="hidden" name="[% INPUT.name |html %]" value="[% INPUT.value |html %]" />
48
	[% END %]
47
[% END %]
49
[% END %]
48
<p><label for="userid">Username:</label>
50
<p><label for="userid">Username:</label>
49
<input type="text" name="userid" id="userid" class="input focus" value="[% userid %]" size="20" tabindex="1" />
51
<input type="text" name="userid" id="userid" class="input focus" value="[% userid %]" size="20" tabindex="1" />
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt (-1 / +3 lines)
Lines 142-148 Link Here
142
                                <input type="hidden" name="koha_login_context" value="opac" />
142
                                <input type="hidden" name="koha_login_context" value="opac" />
143
                                <fieldset class="brief">
143
                                <fieldset class="brief">
144
                                    [% FOREACH INPUT IN INPUTS %]
144
                                    [% FOREACH INPUT IN INPUTS %]
145
                                        <input type="hidden" name="[% INPUT.name |html %]" value="[% INPUT.value |html %]" />
145
                                        [% UNLESS INPUT.name == 'logout.x' #No reason to send the logout-signal again %]
146
                                            <input type="hidden" name="[% INPUT.name |html %]" value="[% INPUT.value |html %]" />
147
                                        [% END %]
146
                                    [% END %]
148
                                    [% END %]
147
                                    <label for="userid">Login</label>
149
                                    <label for="userid">Login</label>
148
                                    <input type="text"  size="25" id="userid"  name="userid" />
150
                                    <input type="text"  size="25" id="userid"  name="userid" />
(-)a/opac/opac-user.pl (-1 / +1 lines)
Lines 349-355 foreach my $res (@reserves) { Link Here
349
$template->param( WAITING => \@waiting );
349
$template->param( WAITING => \@waiting );
350
350
351
# current alert subscriptions
351
# current alert subscriptions
352
my $alerts = getalert($borrowernumber);
352
my $alerts = getalert($borrowernumber) if $borrowernumber; #Superuser has no borrowernumber
353
foreach ( @$alerts ) {
353
foreach ( @$alerts ) {
354
    $_->{ $_->{type} } = 1;
354
    $_->{ $_->{type} } = 1;
355
    $_->{relatedto} = findrelatedto( $_->{type}, $_->{externalid} );
355
    $_->{relatedto} = findrelatedto( $_->{type}, $_->{externalid} );
(-)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