3 # This file is part of Koha.
5 # Copyright (C) 2017 Catalyst IT
6 # Copyright 2021 Koha Development team
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
21 #This selenium test is to test authentication, by performing the following: create a category and patron (same as basic_workflow.t). Then the superlibrarian logs out and the created patron must log into the staff intranet and OPAC
23 #Note: If you are testing this on kohadevbox with selenium installed in kohadevbox then you need to set the staffClientBaseURL to localhost:8080 and the OPACBaseURL to localhost:80
26 use Test::More tests => 2;
32 use t::lib::TestBuilder;
37 eval { require Selenium::Remote::Driver; };
38 skip "Selenium::Remote::Driver is needed for selenium tests.", 2 if $@;
40 my $builder = t::lib::TestBuilder->new;
41 my $s = t::lib::Selenium->new;
42 my $driver = $s->driver;
44 subtest 'Staff interface authentication' => sub {
46 my $mainpage = $s->base_url . q|mainpage.pl|;
47 $driver->get($mainpage);
48 like( $driver->get_title, qr(Log in to Koha), 'Hitting the main page should redirect to the login form');
50 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 0 }});
51 my $password = Koha::AuthUtils::generate_password($patron->category);
52 t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
53 $patron->set_password({ password => $password });
55 # Patron does not have permission to access staff interface
56 $s->auth( $patron->userid, $password );
57 like( $driver->get_title, qr(Access denied), 'Patron without permission should be redirected to the login form' );
59 $driver->get($mainpage . q|?logout.x=1|);
60 $patron->flags(4)->store; # catalogue permission
61 $s->auth( $patron->userid, $password );
62 like( $driver->get_title, qr(Koha staff interface), 'Patron with flags catalogue should be able to login' );
64 $driver->get($mainpage . q|?logout.x=1|);
65 like( $driver->get_title(), qr(Log in to Koha), 'If logout is requested, login form should be displayed' );
67 $patron->flags(1)->store; # superlibrarian permission
68 $s->auth( $patron->userid, $password );
69 like( $driver->get_title, qr(Koha staff interface), 'Patron with flags superlibrarian should be able to login' );
71 subtest 'not authorized' => sub {
75 $driver->get($mainpage . q|?logout.x=1|);
76 $patron->flags(4)->store; # Patron has only catalogue permission
77 like( $driver->get_title, qr(Log in to Koha), 'Patron should hit the login form after logout' );
79 $s->fill_form({ userid => $patron->userid, password => $password });
80 $s->driver->find_element('//input[@id="submit-button"]')->click;
82 my $cookie = $driver->get_cookie_named('CGISESSID');
83 my $first_sessionID = $cookie->{value};
85 # Patron is logged in and got a CGISESSID cookie, miam
86 like( $driver->get_title, qr(Koha staff interface), 'Patron is logged in' );
87 $cookie = $driver->get_cookie_named('CGISESSID');
88 is( $cookie->{value}, $first_sessionID, 'no new session after login, the session has been upgraded' );
90 # Authorized page can be accessed, cookie does not change
91 $driver->get( $s->base_url . q|catalogue/search.pl| );
92 like( $driver->get_title, qr(Advanced search), 'Patron can access advanced search' );
93 $cookie = $driver->get_cookie_named('CGISESSID');
94 is( $cookie->{value}, $first_sessionID, 'no new session after hit' );
96 # Unauthorized page redirect to the login form
97 $driver->get( $s->base_url . q|circ/circulation.pl| );
98 like( $driver->get_title, qr(Access denied), 'Patron cannot access the circulation module' );
99 # But the patron does not lose the CGISESSID cookie!
100 $cookie = $driver->get_cookie_named('CGISESSID');
101 is( $cookie->{value}, $first_sessionID, 'no new session if unauthorized page is hit' );
103 # Luckily mainpage can still be accessed
104 $s->click( { id => 'mainpage', main_class => 'main container-fluid' } );
105 like( $driver->get_title, qr(Koha staff interface), 'Patron can come back to the mainpage' );
106 $cookie = $driver->get_cookie_named('CGISESSID');
107 is( $cookie->{value}, $first_sessionID, 'no new session if back to the mainpage' );
109 # As well as the search
110 $driver->get( $s->base_url . q|catalogue/search.pl| );
111 like( $driver->get_title, qr(Advanced search), 'Patron can access advanced search' );
112 # But circulation module is prohibided!
113 $driver->get( $s->base_url . q|circ/circulation.pl| );
114 like( $driver->get_title, qr(Access denied), 'Patron cannot access the circulation module' );
115 # Still can reuse the same cookie
116 $cookie = $driver->get_cookie_named('CGISESSID');
117 is( $cookie->{value}, $first_sessionID, 'no new session if unauthorized page is hit' );
119 # This is the "previous page" using the back() JS
120 $s->click( { id => 'previous_page', main_class => 'main container-fluid' } );
121 like( $driver->get_title, qr(Advanced search), 'Patron can come back to the previous page' );
122 $cookie = $driver->get_cookie_named('CGISESSID');
123 is( $cookie->{value}, $first_sessionID, 'no new session if back to the previous page' );
125 # Check with a script that is using check_cookie_auth, session must not be deleted!
126 $driver->get( $s->base_url . q|svc/checkouts| );
127 #FIXME - 500 is the current behaviour, but it's not nice. It could be improved.
128 like( $driver->get_title, qr(Error 500), 'Patron cannot access svc script where circulate permissions are required');
129 $driver->get( $s->base_url . q|catalogue/search.pl| );
130 like( $driver->get_title, qr(Advanced search), 'Patron can reuse the cookie after a script that used check_cookie_auth' );
131 $cookie = $driver->get_cookie_named('CGISESSID');
132 is( $cookie->{value}, $first_sessionID, 'no new session if unauthorized page is hit' );
134 push @data_to_cleanup, $patron, $patron->category, $patron->library;
137 subtest 'OPAC interface authentication' => sub {
140 my $mainpage = $s->opac_base_url . q|opac-main.pl|;
142 $driver->get($mainpage . q|?logout.x=1|); # Disconnect first! We are logged in if staff and opac interfaces are separated by ports
144 $driver->get($mainpage);
145 like( $driver->get_title, qr(Koha online catalog), 'Hitting the main page should not redirect to the login form');
147 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 0 }});
148 my $password = Koha::AuthUtils::generate_password($patron->category);
149 t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
150 $patron->set_password({ password => $password });
153 $driver->find_element('//a[@class="nav-link login-link loginModal-trigger"]')->click;
154 $s->fill_form( { muserid => $patron->userid, mpassword => $password } );
155 $driver->find_element('//div[@id="loginModal"]//input[@type="submit"]')->click;
156 like( $driver->get_title, qr(Koha online catalog), 'Patron without permission should be able to login to the OPAC using the modal' );
157 $driver->find_element('//div[@id="userdetails"]');
158 like( $driver->get_title, qr(Your library home), 'Patron without permissions should be able to login to the OPAC using the modal');
160 $driver->find_element('//a[@id="user-menu"]')->click;
161 $driver->find_element('//a[@id="logout"]')->click;
162 $driver->get($mainpage); # This should not be needed but we the next find_element fails randomly
165 $driver->error_handler(
167 my ( $driver, $selenium_error ) = @_;
168 print STDERR "\nSTRACE:";
170 while ( (my @call_details = (caller($i++))) ){
171 print STDERR "\t" . $call_details[1]. ":" . $call_details[2] . " in " . $call_details[3]."\n";
174 print STDERR sprintf("Is logged in patron: %s (%s)?\n", $patron->firstname, $patron->surname );
175 $s->capture( $driver );
176 croak $selenium_error;
180 $driver->find_element('//div[@id="login"]'); # logged out
181 $s->add_error_handler; # Reset to the default error handler
184 # Using the form on the right
185 $s->fill_form( { userid => $patron->userid, password => $password } );
187 $driver->find_element('//div[@id="userdetails"]');
188 like( $driver->get_title, qr(Your library home), 'Patron without permissions should be able to login to the OPAC using the form on the right');
190 $driver->find_element('//a[@id="user-menu"]')->click;
191 $driver->find_element('//a[@id="logout"]')->click;
192 $driver->find_element('//div[@id="login"]'); # logged out
195 $patron->flags(4)->store; # catalogue permission
196 $s->fill_form( { userid => $patron->userid, password => $password } );
198 $driver->find_element('//div[@id="userdetails"]');
199 like( $driver->get_title, qr(Your library home), 'Patron with catalogue permission should be able to login to the OPAC');
201 $driver->find_element('//a[@id="user-menu"]')->click;
202 $driver->find_element('//a[@id="logout"]')->click;
203 $driver->find_element('//div[@id="login"]'); # logged out
205 $patron->flags(1)->store; # superlibrarian permission
206 $s->fill_form( { userid => $patron->userid, password => $password } );
208 $driver->find_element('//div[@id="userdetails"]');
209 like( $driver->get_title, qr(Your library home), 'Patron with superlibrarian permission should be able to login to the OPAC');
211 $driver->find_element('//a[@id="user-menu"]')->click;
212 $driver->find_element('//a[@id="logout"]')->click;
213 $driver->find_element('//div[@id="login"]'); # logged out
215 subtest 'not authorized' => sub {
218 $driver->get($mainpage . q|?logout.x=1|);
219 $driver->get($mainpage);
220 my $cookie = $driver->get_cookie_named('CGISESSID');
221 my $first_sessionID = $cookie->{value};
223 # User is not logged in, navigation does not generate a new cookie
224 $driver->get( $s->opac_base_url . q|opac-search.pl| );
225 like( $driver->get_title, qr(Advanced search) );
226 $cookie = $driver->get_cookie_named('CGISESSID');
227 is( $cookie->{value}, $first_sessionID, );
230 $driver->get($mainpage);
231 $s->fill_form( { userid => $patron->userid, password => $password } );
234 # After logged in, the same cookie is reused
235 like( $driver->get_title, qr(Your library home) );
236 $cookie = $driver->get_cookie_named('CGISESSID');
237 is( $cookie->{value}, $first_sessionID, );
238 $driver->get( $s->opac_base_url . q|opac-search.pl| );
239 like( $driver->get_title, qr(Advanced search) );
240 $cookie = $driver->get_cookie_named('CGISESSID');
241 is( $cookie->{value}, $first_sessionID, );
243 # Logged in user can place holds
244 $driver->get( $s->opac_base_url . q|opac-reserve.pl| ); # We may need to pass a biblionumber here in the future
245 like( $driver->get_title, qr(Placing a hold) );
246 $cookie = $driver->get_cookie_named('CGISESSID');
247 is( $cookie->{value}, $first_sessionID, );
249 $driver->get($mainpage . q|?logout.x=1|);
251 # FIXME This new get should not be needed, but the cookie is not modified right after logout
252 # However it's not the behavour when testing the UI
253 $driver->get($mainpage);
255 # After logout a new cookie is generated, the previous session has been deleted
256 $cookie = $driver->get_cookie_named('CGISESSID');
257 isnt( $cookie->{value}, $first_sessionID, );
258 $first_sessionID = $cookie->{value};
260 $driver->get( $s->opac_base_url . q|svc/checkout_notes| );
261 #FIXME - 500 is the current behaviour, but it's not nice. It could be improved.
262 like( $driver->get_title, qr(An error has occurred), 'Patron cannot access svc');
263 # No new cookie generated
264 $cookie = $driver->get_cookie_named('CGISESSID');
265 is( $cookie->{value}, $first_sessionID, );
267 $driver->get( $s->opac_base_url . q|opac-reserve.pl| );
268 like( $driver->get_title, qr(Log in to your account) );
270 # Still no new cookie generated
271 $driver->get($mainpage);
272 $cookie = $driver->get_cookie_named('CGISESSID');
273 is( $cookie->{value}, $first_sessionID, );
276 push @data_to_cleanup, $patron, $patron->category, $patron->library;
283 $_->delete for @data_to_cleanup;