| Lines 20-26
          use Modern::Perl;
      
      
        Link Here | 
        
          | 20 | use Test::More tests => 2; | 20 | use Test::More tests => 2; | 
        
          | 21 |  | 21 |  | 
        
          | 22 | use Test::Mojo; | 22 | use Test::Mojo; | 
            
              | 23 | use Test::Warn; |  |  | 
        
          | 24 |  | 23 |  | 
        
          | 25 | use t::lib::TestBuilder; | 24 | use t::lib::TestBuilder; | 
        
          | 26 | use t::lib::Mocks; | 25 | use t::lib::Mocks; | 
  
    | Lines 30-41
          use Koha::Patrons;
      
      
        Link Here | 
        
          | 30 | my $schema  = Koha::Database->new->schema; | 29 | my $schema  = Koha::Database->new->schema; | 
        
          | 31 | my $builder = t::lib::TestBuilder->new; | 30 | my $builder = t::lib::TestBuilder->new; | 
        
          | 32 |  | 31 |  | 
          
            
              | 33 | # FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling | 32 | t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); | 
            
              | 34 | # this affects the other REST api tests |  |  | 
            
              | 35 | t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' ); | 
        
          | 36 |  | 33 |  | 
          
            
              | 37 | my $remote_address = '127.0.0.1'; | 34 | my $t = Test::Mojo->new('Koha::REST::V1'); | 
            
              | 38 | my $t              = Test::Mojo->new('Koha::REST::V1'); |  |  | 
        
          | 39 |  | 35 |  | 
        
          | 40 | subtest 'set() (authorized user tests)' => sub { | 36 | subtest 'set() (authorized user tests)' => sub { | 
        
          | 41 |  | 37 |  | 
  
    | Lines 43-122
          subtest 'set() (authorized user tests)' => sub {
      
      
        Link Here | 
        
          | 43 |  | 39 |  | 
        
          | 44 |     $schema->storage->txn_begin; | 40 |     $schema->storage->txn_begin; | 
        
          | 45 |  | 41 |  | 
          
            
              | 46 |     my ( $patron, $session ) = create_user_and_session({ authorized => 1 }); | 42 |     my $privileged_patron = $builder->build_object( | 
            
              |  |  | 43 |         { | 
            
              | 44 |             class => 'Koha::Patrons', | 
            
              | 45 |             value => { flags => 1 } | 
            
              | 46 |         } | 
            
              | 47 |     ); | 
            
              | 48 |     my $password = 'thePassword123'; | 
            
              | 49 |     $privileged_patron->set_password( | 
            
              | 50 |         { password => $password, skip_validation => 1 } ); | 
            
              | 51 |     my $userid = $privileged_patron->userid; | 
            
              | 52 |  | 
            
              | 53 |     my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); | 
        
          | 47 |  | 54 |  | 
        
          | 48 |     t::lib::Mocks::mock_preference( 'minPasswordLength',     3 ); | 55 |     t::lib::Mocks::mock_preference( 'minPasswordLength',     3 ); | 
        
          | 49 |     t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 ); | 56 |     t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 ); | 
        
          | 50 |  | 57 |  | 
        
          | 51 |     my $new_password = 'abc'; | 58 |     my $new_password = 'abc'; | 
        
          | 52 |  | 59 |  | 
          
            
              | 53 |     my $tx | 60 |     $t->post_ok( "//$userid:$password@/api/v1/patrons/" | 
            
              | 54 |         = $t->ua->build_tx( POST => "/api/v1/patrons/" | 61 |           . $patron->id | 
            
              | 55 |             . $patron->id | 62 |           . "/password" => json => | 
            
              | 56 |             . "/password" => json => { password => $new_password, password_2 => $new_password } ); | 63 |           { password => $new_password, password_2 => $new_password } ) | 
            
              |  |  | 64 |       ->status_is(200)->json_is(''); | 
        
          | 57 |  | 65 |  | 
          
            
              | 58 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 66 |     $t->post_ok( "//$userid:$password@/api/v1/patrons/" | 
            
              | 59 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 67 |           . $patron->id | 
            
              | 60 |     $t->request_ok($tx)->status_is(200)->json_is( '' ); | 68 |           . "/password" => json => | 
            
              | 61 |  | 69 |           { password => $new_password, password_2 => 'cde' } )->status_is(400) | 
            
              | 62 |     $tx | 70 |       ->json_is( { error => 'Passwords don\'t match' } ); | 
            
              | 63 |         = $t->ua->build_tx( POST => "/api/v1/patrons/" |  |  | 
            
              | 64 |             . $patron->id | 
            
              | 65 |             . "/password" => json => { password => $new_password, password_2 => 'cde' } ); | 
            
              | 66 |  | 
            
              | 67 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 
            
              | 68 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 
            
              | 69 |     $t->request_ok($tx)->status_is(400)->json_is({ error => 'Passwords don\'t match' }); | 
        
          | 70 |  | 71 |  | 
        
          | 71 |     t::lib::Mocks::mock_preference( 'minPasswordLength', 5 ); | 72 |     t::lib::Mocks::mock_preference( 'minPasswordLength', 5 ); | 
            
              | 72 |     $tx |  |  | 
            
              | 73 |         = $t->ua->build_tx( POST => "/api/v1/patrons/" | 
            
              | 74 |             . $patron->id | 
            
              | 75 |             . "/password" => json => { password => $new_password, password_2 => $new_password } ); | 
        
          | 76 |  | 73 |  | 
          
            
              | 77 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 74 |     $t->post_ok( "//$userid:$password@/api/v1/patrons/" | 
            
              | 78 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 75 |           . $patron->id | 
            
              | 79 |     $t->request_ok($tx)->status_is(400)->json_is({ error => 'Password length (3) is shorter than required (5)' }); | 76 |           . "/password" => json => | 
            
              |  |  | 77 |           { password => $new_password, password_2 => $new_password } ) | 
            
              | 78 |       ->status_is(400) | 
            
              | 79 |       ->json_is( | 
            
              | 80 |         { error => 'Password length (3) is shorter than required (5)' } ); | 
        
          | 80 |  | 81 |  | 
        
          | 81 |     $new_password = 'abc   '; | 82 |     $new_password = 'abc   '; | 
          
            
              | 82 |     $tx | 83 |     $t->post_ok( "//$userid:$password@/api/v1/patrons/" | 
            
              | 83 |         = $t->ua->build_tx( POST => "/api/v1/patrons/" | 84 |           . $patron->id | 
            
              | 84 |             . $patron->id | 85 |           . "/password" => json => | 
            
              | 85 |             . "/password" => json => { password => $new_password, password_2 => $new_password } ); | 86 |           { password => $new_password, password_2 => $new_password } ) | 
            
              | 86 |  | 87 |       ->status_is(400)->json_is( | 
            
              | 87 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 88 |         { | 
            
              | 88 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 89 |             error => | 
            
              | 89 |     $t->request_ok($tx)->status_is(400)->json_is({ error => '[Password contains leading/trailing whitespace character(s)]' }); | 90 |               '[Password contains leading/trailing whitespace character(s)]' | 
            
              |  |  | 91 |         } | 
            
              | 92 |       ); | 
        
          | 90 |  | 93 |  | 
        
          | 91 |     $new_password = 'abcdefg'; | 94 |     $new_password = 'abcdefg'; | 
          
            
              | 92 |     $tx | 95 |     $t->post_ok( "//$userid:$password@/api/v1/patrons/" | 
            
              | 93 |         = $t->ua->build_tx( POST => "/api/v1/patrons/" | 96 |           . $patron->id | 
            
              | 94 |             . $patron->id | 97 |           . "/password" => json => | 
            
              | 95 |             . "/password" => json => { password => $new_password, password_2 => $new_password } ); | 98 |           { password => $new_password, password_2 => $new_password } ) | 
            
              | 96 |  | 99 |       ->status_is(200)->json_is(''); | 
            
              | 97 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 100 |  | 
            
              | 98 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 101 |     t::lib::Mocks::mock_preference( 'RequireStrongPassword', 1 ); | 
            
              | 99 |     $t->request_ok($tx)->status_is(200)->json_is(''); | 102 |     $t->post_ok( "//$userid:$password@/api/v1/patrons/" | 
            
              | 100 |  | 103 |           . $patron->id | 
            
              | 101 |     t::lib::Mocks::mock_preference( 'RequireStrongPassword', 1); | 104 |           . "/password" => json => | 
            
              | 102 |     $tx | 105 |           { password => $new_password, password_2 => $new_password } ) | 
            
              | 103 |         = $t->ua->build_tx( POST => "/api/v1/patrons/" | 106 |       ->status_is(400)->json_is( { error => '[Password is too weak]' } ); | 
            
              | 104 |             . $patron->id |  |  | 
            
              | 105 |             . "/password" => json => { password => $new_password, password_2 => $new_password } ); | 
            
              | 106 |  | 
            
              | 107 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 
            
              | 108 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 
            
              | 109 |     $t->request_ok($tx)->status_is(400)->json_is({ error => '[Password is too weak]' }); | 
        
          | 110 |  | 107 |  | 
        
          | 111 |     $new_password = 'ABcde123%&'; | 108 |     $new_password = 'ABcde123%&'; | 
          
            
              | 112 |     $tx | 109 |     $t->post_ok( "//$userid:$password@/api/v1/patrons/" | 
            
              | 113 |         = $t->ua->build_tx( POST => "/api/v1/patrons/" | 110 |           . $patron->id | 
            
              | 114 |             . $patron->id | 111 |           . "/password" => json => | 
            
              | 115 |             . "/password" => json => { password => $new_password, password_2 => $new_password } ); | 112 |           { password => $new_password, password_2 => $new_password } ) | 
            
              | 116 |  | 113 |       ->status_is(200)->json_is(''); | 
            
              | 117 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); |  |  | 
            
              | 118 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 
            
              | 119 |     $t->request_ok($tx)->status_is(200)->json_is(''); | 
        
          | 120 |  | 114 |  | 
        
          | 121 |     $schema->storage->txn_rollback; | 115 |     $schema->storage->txn_rollback; | 
        
          | 122 | }; | 116 | }; | 
  
    | Lines 127-134
          subtest 'set_public() (unprivileged user tests)' => sub {
      
      
        Link Here | 
        
          | 127 |  | 121 |  | 
        
          | 128 |     $schema->storage->txn_begin; | 122 |     $schema->storage->txn_begin; | 
        
          | 129 |  | 123 |  | 
          
            
              | 130 |     my ( $patron, $session ) = create_user_and_session({ authorized => 0 }); | 124 |     my $patron   = $builder->build_object( { class => 'Koha::Patrons' } ); | 
            
              | 131 |     my $other_patron = $builder->build_object({ class => 'Koha::Patrons' }); | 125 |     my $password = 'thePassword123'; | 
            
              |  |  | 126 |     $patron->set_password( { password => $password, skip_validation => 1 } ); | 
            
              | 127 |     my $userid       = $patron->userid; | 
            
              | 128 |     my $other_patron = $builder->build_object( { class => 'Koha::Patrons' } ); | 
        
          | 132 |  | 129 |  | 
        
          | 133 |     # Enable the public API | 130 |     # Enable the public API | 
        
          | 134 |     t::lib::Mocks::mock_preference( 'RESTPublicAPI', 1 ); | 131 |     t::lib::Mocks::mock_preference( 'RESTPublicAPI', 1 ); | 
  
    | Lines 139-256
          subtest 'set_public() (unprivileged user tests)' => sub {
      
      
        Link Here | 
        
          | 139 |  | 136 |  | 
        
          | 140 |     my $new_password = 'abc'; | 137 |     my $new_password = 'abc'; | 
        
          | 141 |  | 138 |  | 
          
            
              | 142 |     my $tx = $t->ua->build_tx( | 139 |     $t->post_ok( | 
            
              | 143 |               POST => "/api/v1/public/patrons/" | 140 |             "//$userid:$password@/api/v1/public/patrons/" | 
            
              | 144 |             . $patron->id | 141 |           . $patron->id | 
            
              | 145 |             . "/password" => json => { | 142 |           . "/password" => json => { | 
        
          | 146 |             password          => $new_password, | 143 |             password          => $new_password, | 
        
          | 147 |             password_repeated => $new_password, | 144 |             password_repeated => $new_password, | 
        
          | 148 |             old_password      => 'blah' | 145 |             old_password      => 'blah' | 
          
            
              | 149 |             } | 146 |           } | 
            
              |  |  | 147 |     )->status_is(403)->json_is( | 
            
              | 148 |         { | 
            
              | 149 |             error => | 
            
              | 150 |               'Configuration prevents password changes by unprivileged users' | 
            
              | 151 |         } | 
        
          | 150 |     ); | 152 |     ); | 
        
          | 151 |  | 153 |  | 
            
              | 152 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); |  |  | 
            
              | 153 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 
            
              | 154 |     $t->request_ok($tx) | 
            
              | 155 |       ->status_is(403) | 
            
              | 156 |       ->json_is({ | 
            
              | 157 |           error => 'Configuration prevents password changes by unprivileged users' | 
            
              | 158 |         }); | 
            
              | 159 |  | 
        
          | 160 |     t::lib::Mocks::mock_preference( 'OpacPasswordChange', 1 ); | 154 |     t::lib::Mocks::mock_preference( 'OpacPasswordChange', 1 ); | 
        
          | 161 |  | 155 |  | 
          
            
              | 162 |     my $password = 'holapassword'; | 156 |     $t->post_ok( | 
            
              | 163 |     $patron->set_password({ password => $password }); | 157 |             "//$userid:$password@/api/v1/public/patrons/" | 
            
              | 164 |     $tx = $t->ua->build_tx( | 158 |           . $other_patron->id | 
            
              | 165 |               POST => "/api/v1/public/patrons/" | 159 |           . "/password" => json => { | 
            
              | 166 |             . $other_patron->id |  |  | 
            
              | 167 |             . "/password" => json => { | 
        
          | 168 |             password          => $new_password, | 160 |             password          => $new_password, | 
        
          | 169 |             password_repeated => $new_password, | 161 |             password_repeated => $new_password, | 
        
          | 170 |             old_password      => $password | 162 |             old_password      => $password | 
          
            
              | 171 |             } | 163 |           } | 
            
              | 172 |     ); | 164 |     )->status_is(403) | 
            
              | 173 |  | 165 |       ->json_is( '/error', | 
            
              | 174 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 166 |         "Authorization failure. Missing required permission(s)." ); | 
            
              | 175 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 167 |  | 
            
              | 176 |     $t->request_ok($tx) | 168 |     $t->post_ok( | 
            
              | 177 |       ->status_is(403) | 169 |             "//$userid:$password@/api/v1/public/patrons/" | 
            
              | 178 |       ->json_is('/error', "Authorization failure. Missing required permission(s)."); | 170 |           . $patron->id | 
            
              | 179 |  | 171 |           . "/password" => json => { | 
            
              | 180 |     $tx = $t->ua->build_tx( |  |  | 
            
              | 181 |               POST => "/api/v1/public/patrons/" | 
            
              | 182 |             . $patron->id | 
            
              | 183 |             . "/password" => json => { | 
        
          | 184 |             password          => $new_password, | 172 |             password          => $new_password, | 
        
          | 185 |             password_repeated => 'wrong_password', | 173 |             password_repeated => 'wrong_password', | 
        
          | 186 |             old_password      => $password | 174 |             old_password      => $password | 
          
            
              | 187 |             } | 175 |           } | 
            
              |  |  | 176 |     )->status_is(400)->json_is( | 
            
              | 177 |         { | 
            
              | 178 |             error => "Passwords don't match" | 
            
              | 179 |         } | 
        
          | 188 |     ); | 180 |     ); | 
        
          | 189 |  | 181 |  | 
          
            
              | 190 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 182 |     $t->post_ok( | 
            
              | 191 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 183 |             "//$userid:$password@/api/v1/public/patrons/" | 
            
              | 192 |     $t->request_ok($tx) | 184 |           . $patron->id | 
            
              | 193 |       ->status_is(400) | 185 |           . "/password" => json => { | 
            
              | 194 |       ->json_is({ |  |  | 
            
              | 195 |           error => "Passwords don't match" | 
            
              | 196 |         }); | 
            
              | 197 |  | 
            
              | 198 |     $tx = $t->ua->build_tx( | 
            
              | 199 |               POST => "/api/v1/public/patrons/" | 
            
              | 200 |             . $patron->id | 
            
              | 201 |             . "/password" => json => { | 
        
          | 202 |             password          => $new_password, | 186 |             password          => $new_password, | 
        
          | 203 |             password_repeated => $new_password, | 187 |             password_repeated => $new_password, | 
        
          | 204 |             old_password      => 'badpassword' | 188 |             old_password      => 'badpassword' | 
          
            
              | 205 |             } | 189 |           } | 
            
              |  |  | 190 |     )->status_is(400)->json_is( | 
            
              | 191 |         { | 
            
              | 192 |             error => "Invalid password" | 
            
              | 193 |         } | 
        
          | 206 |     ); | 194 |     ); | 
        
          | 207 |  | 195 |  | 
          
            
              | 208 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 196 |     $t->post_ok( | 
            
              | 209 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 197 |             "//$userid:$password@/api/v1/public/patrons/" | 
            
              | 210 |     $t->request_ok($tx) | 198 |           . $patron->id | 
            
              | 211 |       ->status_is(400) | 199 |           . "/password" => json => { | 
            
              | 212 |       ->json_is({ |  |  | 
            
              | 213 |           error => "Invalid password" | 
            
              | 214 |         }); | 
            
              | 215 |  | 
            
              | 216 |     $tx = $t->ua->build_tx( | 
            
              | 217 |               POST => "/api/v1/public/patrons/" | 
            
              | 218 |             . $patron->id | 
            
              | 219 |             . "/password" => json => { | 
        
          | 220 |             password          => $new_password, | 200 |             password          => $new_password, | 
        
          | 221 |             password_repeated => $new_password, | 201 |             password_repeated => $new_password, | 
        
          | 222 |             old_password      => $password | 202 |             old_password      => $password | 
          
            
              | 223 |             } | 203 |           } | 
            
              | 224 |     ); | 204 |     )->status_is(200)->json_is(''); | 
            
              | 225 |  |  |  | 
            
              | 226 |     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); | 
            
              | 227 |     $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); | 
            
              | 228 |     $t->request_ok($tx) | 
            
              | 229 |       ->status_is(200) | 
            
              | 230 |       ->json_is(''); | 
        
          | 231 |  | 205 |  | 
        
          | 232 |     $schema->storage->txn_rollback; | 206 |     $schema->storage->txn_rollback; | 
        
          | 233 | }; | 207 | }; | 
            
              | 234 |  |  |  | 
            
              | 235 | sub create_user_and_session { | 
            
              | 236 |  | 
            
              | 237 |     my ( $args ) = @_; | 
            
              | 238 |     my $flags = ( $args->{authorized} ) ? 16 : 0; | 
            
              | 239 |  | 
            
              | 240 |     my $user = $builder->build_object( | 
            
              | 241 |         { | 
            
              | 242 |             class => 'Koha::Patrons', | 
            
              | 243 |             value => { flags => $flags } | 
            
              | 244 |         } | 
            
              | 245 |     ); | 
            
              | 246 |  | 
            
              | 247 |     # Create a session for the authorized user | 
            
              | 248 |     my $session = C4::Auth::get_session(''); | 
            
              | 249 |     $session->param( 'number',   $user->borrowernumber ); | 
            
              | 250 |     $session->param( 'id',       $user->userid ); | 
            
              | 251 |     $session->param( 'ip',       '127.0.0.1' ); | 
            
              | 252 |     $session->param( 'lasttime', time() ); | 
            
              | 253 |     $session->flush; | 
            
              | 254 |  | 
            
              | 255 |     return ( $user, $session ); | 
            
              | 256 | } | 
            
              | 257 | -  |