| 
      
            Line 0
          
      
      
        Link Here
      
     | 
  
            
               | 
               | 
              1 | 
              package Koha::REST::V1::Plugins::KohaliciousSwagtenticator;  | 
            
            
              | 2 | 
               | 
            
            
              | 3 | 
              use Modern::Perl;  | 
            
            
              | 4 | 
               | 
            
            
              | 5 | 
              use base qw(Mojolicious::Plugin::Swagger2);  | 
            
            
              | 6 | 
               | 
            
            
              | 7 | 
              use Digest::SHA qw(hmac_sha256_hex);  | 
            
            
              | 8 | 
              use Try::Tiny;  | 
            
            
              | 9 | 
              use Scalar::Util qw(blessed);  | 
            
            
              | 10 | 
               | 
            
            
              | 11 | 
              use C4::Auth;  | 
            
            
              | 12 | 
               | 
            
            
              | 13 | 
              use Koha::Exception::BadAuthenticationToken;  | 
            
            
              | 14 | 
              use Koha::Exception::UnknownProgramState;  | 
            
            
              | 15 | 
              use Koha::Exception::NoPermission;  | 
            
            
              | 16 | 
               | 
            
            
              | 17 | 
              use constant DEBUG => $ENV{SWAGGER2_DEBUG} || 0; | 
            
            
              | 18 | 
               | 
            
            
              | 19 | 
               | 
            
            
              | 20 | 
               | 
            
            
              | 21 | 
              ################################################################################  | 
            
            
              | 22 | 
              ######################  STARTING OVERLOADING SUBROUTINES  ######################  | 
            
            
              | 23 | 
              ################################################################################  | 
            
            
              | 24 | 
               | 
            
            
              | 25 | 
               | 
            
            
              | 26 | 
               | 
            
            
              | 27 | 
              =head _generate_request_handler  | 
            
            
              | 28 | 
              @OVERLOADS Mojolicious::Plugin::Swagger2::_generate_request_handler()  | 
            
            
              | 29 | 
              This is just a copy-paste of the parent function with a small incision to inject the Koha-authentication mechanism.  | 
            
            
              | 30 | 
              Keep code changes minimal for upstream compatibility, so when problems arise, copy-pasting fixes them!  | 
            
            
              | 31 | 
               | 
            
            
              | 32 | 
              =cut  | 
            
            
              | 33 | 
               | 
            
            
              | 34 | 
              sub _generate_request_handler { | 
            
            
              | 35 | 
                my ($self, $method, $config) = @_;  | 
            
            
              | 36 | 
                my $controller = $config->{'x-mojo-controller'} || $self->{controller};    # back compat | 
            
            
              | 37 | 
               | 
            
            
              | 38 | 
                return sub { | 
            
            
              | 39 | 
                  my $c = shift;  | 
            
            
              | 40 | 
                  my $method_ref;  | 
            
            
              | 41 | 
               | 
            
            
              | 42 | 
                  unless (eval "require $controller;1") { | 
            
            
              | 43 | 
                    $c->app->log->error($@);  | 
            
            
              | 44 | 
                    return $c->render_swagger($self->_not_implemented('Controller not implemented.'), {}, 501); | 
            
            
              | 45 | 
                  }  | 
            
            
              | 46 | 
                  unless ($method_ref = $controller->can($method)) { | 
            
            
              | 47 | 
                    $method_ref = $controller->can(sprintf '%s_%s', $method, lc $c->req->method)  | 
            
            
              | 48 | 
                      and warn "HTTP method name is not used in method name lookup anymore!";  | 
            
            
              | 49 | 
                  }  | 
            
            
              | 50 | 
                  unless ($method_ref) { | 
            
            
              | 51 | 
                    $c->app->log->error(  | 
            
            
              | 52 | 
                      qq(Can't locate object method "$method" via package "$controller. (Something is wrong in @{[$self->url]})")); | 
            
            
              | 53 | 
                    return $c->render_swagger($self->_not_implemented('Method not implemented.'), {}, 501); | 
            
            
              | 54 | 
                  }  | 
            
            
              | 55 | 
                  ### Koha-overload starts here ###  | 
            
            
              | 56 | 
                  ## Check for user api-key authentication and permissions.  | 
            
            
              | 57 | 
                  my ($error, $data, $statusCode) = _koha_authenticate($c, $config);  | 
            
            
              | 58 | 
                  return $c->render_swagger($error, $data, $statusCode) if $error;  | 
            
            
              | 59 | 
                  ### END OF Koha-overload      ###  | 
            
            
              | 60 | 
               | 
            
            
              | 61 | 
                  bless $c, $controller;    # ugly hack?  | 
            
            
              | 62 | 
               | 
            
            
              | 63 | 
                  $c->delay(  | 
            
            
              | 64 | 
                    sub { | 
            
            
              | 65 | 
                      my ($delay) = @_;  | 
            
            
              | 66 | 
                      my ($v, $input) = $self->_validate_input($c, $config);  | 
            
            
              | 67 | 
               | 
            
            
              | 68 | 
                      return $c->render_swagger($v, {}, 400) unless $v->{valid}; | 
            
            
              | 69 | 
                      return $c->$method_ref($input, $delay->begin);  | 
            
            
              | 70 | 
                    },  | 
            
            
              | 71 | 
                    sub { | 
            
            
              | 72 | 
                      my $delay  = shift;  | 
            
            
              | 73 | 
                      my $data   = shift;  | 
            
            
              | 74 | 
                      my $status = shift || 200;  | 
            
            
              | 75 | 
                      my $format = $config->{responses}{$status} || $config->{responses}{default} || {}; | 
            
            
              | 76 | 
                      my @err    = $self->_validator->validate($data, $format->{schema}); | 
            
            
              | 77 | 
               | 
            
            
              | 78 | 
                      return $c->render_swagger({errors => \@err, valid => Mojo::JSON->false}, $data, 500) if @err; | 
            
            
              | 79 | 
                      return $c->render_swagger({}, $data, $status); | 
            
            
              | 80 | 
                    },  | 
            
            
              | 81 | 
                  );  | 
            
            
              | 82 | 
                };  | 
            
            
              | 83 | 
              }  | 
            
            
              | 84 | 
               | 
            
            
              | 85 | 
               | 
            
            
              | 86 | 
               | 
            
            
              | 87 | 
              =head _validate_input  | 
            
            
              | 88 | 
              @OVERLOADS Mojolicious::Plugin::Swagger2::_validate_input()  | 
            
            
              | 89 | 
               | 
            
            
              | 90 | 
              Validates the parameters from the "Operation Object" from Swagger2 specification.  | 
            
            
              | 91 | 
              Overloading to allow OPTIONAL parameters.  | 
            
            
              | 92 | 
              =cut  | 
            
            
              | 93 | 
               | 
            
            
              | 94 | 
              sub _validate_input { | 
            
            
              | 95 | 
                my ($self, $c, $config) = @_;  | 
            
            
              | 96 | 
                my $headers = $c->req->headers;  | 
            
            
              | 97 | 
                my $query   = $c->req->url->query;  | 
            
            
              | 98 | 
                my (%input, %v);  | 
            
            
              | 99 | 
                warn "[Swagtenticator] Successful subclassing _validate_input()!\n";  | 
            
            
              | 100 | 
               | 
            
            
              | 101 | 
                for my $p (@{$config->{parameters} || []}) { | 
            
            
              | 102 | 
                  my ($in, $name) = @$p{qw( in name )}; | 
            
            
              | 103 | 
                  my ($value, @e);  | 
            
            
              | 104 | 
               | 
            
            
              | 105 | 
                  $value  | 
            
            
              | 106 | 
                    = $in eq 'query'    ? $query->param($name)  | 
            
            
              | 107 | 
                    : $in eq 'path'     ? $c->stash($name)  | 
            
            
              | 108 | 
                    : $in eq 'header'   ? $headers->header($name)  | 
            
            
              | 109 | 
                    : $in eq 'body'     ? $c->req->json  | 
            
            
              | 110 | 
                    : $in eq 'formData' ? $c->req->body_params->to_hash  | 
            
            
              | 111 | 
                    :                     "Invalid 'in' for parameter $name in schema definition";  | 
            
            
              | 112 | 
               | 
            
            
              | 113 | 
                  if (defined $value or $p->{required}) { | 
            
            
              | 114 | 
                    my $type = $p->{type} || 'object'; | 
            
            
              | 115 | 
                    $value += 0 if $type =~ /^(?:integer|number)/ and $value =~ /^\d/;  | 
            
            
              | 116 | 
                    $value = ($value eq 'false' or !$value) ? Mojo::JSON->false : Mojo::JSON->true if $type eq 'boolean';  | 
            
            
              | 117 | 
               | 
            
            
              | 118 | 
                    if ($in eq 'body' or $in eq 'formData') { | 
            
            
              | 119 | 
                      warn "[Swagger2] Validate $in @{[$c->req->body]}\n"; | 
            
            
              | 120 | 
                      push @e, map { $_->{path} = "/$name$_->{path}"; $_; } $self->_validator->validate($value, $p->{schema}); | 
            
            
              | 121 | 
                    }  | 
            
            
              | 122 | 
                    else { | 
            
            
              | 123 | 
                      warn "[Swagger2] Validate $in $name=$value\n";  | 
            
            
              | 124 | 
                      push @e, $self->_validator->validate({$name => $value}, {properties => {$name => $p}}); | 
            
            
              | 125 | 
                    }  | 
            
            
              | 126 | 
                  }  | 
            
            
              | 127 | 
               | 
            
            
              | 128 | 
                  $input{$name} = $value unless @e; | 
            
            
              | 129 | 
                  push @{$v{errors}}, @e; | 
            
            
              | 130 | 
                }  | 
            
            
              | 131 | 
               | 
            
            
              | 132 | 
                $v{valid} = @{$v{errors} || []} ? Mojo::JSON->false : Mojo::JSON->true; | 
            
            
              | 133 | 
                return \%v, \%input;  | 
            
            
              | 134 | 
              }  | 
            
            
              | 135 | 
               | 
            
            
              | 136 | 
               | 
            
            
              | 137 | 
               | 
            
            
              | 138 | 
              ################################################################################  | 
            
            
              | 139 | 
              #########  END OF OVERLOADED SUBROUTINES, STARTING EXTENDED FEATURES  ##########  | 
            
            
              | 140 | 
              ################################################################################  | 
            
            
              | 141 | 
               | 
            
            
              | 142 | 
               | 
            
            
              | 143 | 
               | 
            
            
              | 144 | 
              =head _koha_authenticate  | 
            
            
              | 145 | 
               | 
            
            
              | 146 | 
                  _koha_authenticate($c, $config);  | 
            
            
              | 147 | 
               | 
            
            
              | 148 | 
              Checks all authentications in Koha, and prepares the data for a  | 
            
            
              | 149 | 
              Mojolicious::Plugin::Swagger2->render_swagger($errors, $data, $statusCode) -response  | 
            
            
              | 150 | 
              if authentication failed for some reason.  | 
            
            
              | 151 | 
               | 
            
            
              | 152 | 
              @PARAM1 Mojolicious::Controller or a subclass  | 
            
            
              | 153 | 
              @PARAM2 Reference to HASH, the "Operation Object" from Swagger2.0 specification,  | 
            
            
              | 154 | 
                                          matching the given "Path Item Object"'s HTTP Verb.  | 
            
            
              | 155 | 
              @RETURNS List of: HASH Ref, errors encountered  | 
            
            
              | 156 | 
                                HASH Ref, data to be sent  | 
            
            
              | 157 | 
                                String, status code from the Koha::REST::V1::check_key_auth()  | 
            
            
              | 158 | 
              =cut  | 
            
            
              | 159 | 
               | 
            
            
              | 160 | 
              sub _koha_authenticate { | 
            
            
              | 161 | 
                  my ($c, $config) = @_;  | 
            
            
              | 162 | 
                  my ($error, $data, $statusCode);  | 
            
            
              | 163 | 
               | 
            
            
              | 164 | 
                  try { | 
            
            
              | 165 | 
                      check_key_auth($c, $config);  | 
            
            
              | 166 | 
                  } catch { | 
            
            
              | 167 | 
                    if (blessed($_)) { | 
            
            
              | 168 | 
                      if ($_->isa('Koha::Exception::BadAuthenticationToken') || | 
            
            
              | 169 | 
                          $_->isa('Koha::Exception::NoPermission')    ) { | 
            
            
              | 170 | 
                        $error = {valid => Mojo::JSON->false, errors => [{message => $_->error, path => $c->req->url->path_query}]}; | 
            
            
              | 171 | 
                        $data = {header => {"WWW-Authenticate" => "Where is Koha API authentication publicly instructed?"}}; | 
            
            
              | 172 | 
                        $statusCode = 401;  | 
            
            
              | 173 | 
                      }  | 
            
            
              | 174 | 
                      elsif ($_->isa('Koha::Exception::UnknownProgramState')){ | 
            
            
              | 175 | 
                        $error = {valid => Mojo::JSON->false, errors => [{message => $_->error, path => $c->req->url->path_query}]}; | 
            
            
              | 176 | 
                        $data = {}; | 
            
            
              | 177 | 
                        $statusCode = 500;  | 
            
            
              | 178 | 
                      }  | 
            
            
              | 179 | 
               | 
            
            
              | 180 | 
                    }  | 
            
            
              | 181 | 
               | 
            
            
              | 182 | 
                  };  | 
            
            
              | 183 | 
                  return ($error, $data, $statusCode);  | 
            
            
              | 184 | 
              }  | 
            
            
              | 185 | 
               | 
            
            
              | 186 | 
              =head check_key_auth  | 
            
            
              | 187 | 
               | 
            
            
              | 188 | 
                  my $auth_ok = check_key_auth($c, $opObj);  | 
            
            
              | 189 | 
               | 
            
            
              | 190 | 
              For authentication to succeed, the client have to send 3 custom HTTP  | 
            
            
              | 191 | 
              headers:  | 
            
            
              | 192 | 
               - X-Koha-Username: userid of borrower  | 
            
            
              | 193 | 
               - X-Koha-Timestamp: timestamp of the request  | 
            
            
              | 194 | 
               - X-Koha-Signature: signature of the request  | 
            
            
              | 195 | 
               | 
            
            
              | 196 | 
              The signature is a HMAC-SHA256 hash of several elements of the request,  | 
            
            
              | 197 | 
              separated by spaces:  | 
            
            
              | 198 | 
               - HTTP method (uppercase)  | 
            
            
              | 199 | 
               - URL path and query string  | 
            
            
              | 200 | 
               - username  | 
            
            
              | 201 | 
               - timestamp of the request  | 
            
            
              | 202 | 
               | 
            
            
              | 203 | 
              The server then tries to rebuild the signature with each user's API key.  | 
            
            
              | 204 | 
              If one matches the received X-Koha-Signature, then authentication is  | 
            
            
              | 205 | 
              almost OK.  | 
            
            
              | 206 | 
               | 
            
            
              | 207 | 
              To avoid requests to be replayed, the last request's timestamp is stored  | 
            
            
              | 208 | 
              in database and the authentication succeeds only if the stored timestamp  | 
            
            
              | 209 | 
              is lesser than X-Koha-Timestamp.  | 
            
            
              | 210 | 
               | 
            
            
              | 211 | 
               | 
            
            
              | 212 | 
              There is also an "anonymous" mode if X-Koha-* headers are not set.  | 
            
            
              | 213 | 
              Anonymous mode differ from authenticated mode in one thing: if user is  | 
            
            
              | 214 | 
              authenticated, the corresponding Koha::Borrower object is stored in  | 
            
            
              | 215 | 
              Mojolicious stash, so it can easily be retrieved by controllers.  | 
            
            
              | 216 | 
              Controllers then have the responsibility of what to do if user is  | 
            
            
              | 217 | 
              authenticated or not.  | 
            
            
              | 218 | 
               | 
            
            
              | 219 | 
              @PARAM1 Mojolicious::Controller  | 
            
            
              | 220 | 
              @PARAM2 Reference to HASH, the "Operation Object" from Swagger2.0 specification,  | 
            
            
              | 221 | 
                                          matching the given "Path Item Object"'s HTTP Verb.  | 
            
            
              | 222 | 
              @RETURNS Integer, 1 if authentication succeeded, otherwise throws exceptions.  | 
            
            
              | 223 | 
              @THROWS Koha::Exception::BadAuthenticationToken from _check_key_auth_api_key()  | 
            
            
              | 224 | 
              @THROWS Koha::Exception::NoPermission if borrower has no Koha permission to access the resource  | 
            
            
              | 225 | 
              @THROWS Koha::Exception::UnknownProgramState if authentication system malfunctions;  | 
            
            
              | 226 | 
              =cut  | 
            
            
              | 227 | 
               | 
            
            
              | 228 | 
              sub check_key_auth { | 
            
            
              | 229 | 
                  my ($c, $opObj) = @_;  | 
            
            
              | 230 | 
                  my $req_username = $c->req->headers->header('X-Koha-Username'); | 
            
            
              | 231 | 
                  my $req_timestamp = $c->req->headers->header('X-Koha-Timestamp'); | 
            
            
              | 232 | 
                  my $req_signature = $c->req->headers->header('X-Koha-Signature'); | 
            
            
              | 233 | 
                  my $req_method = $c->req->method;  | 
            
            
              | 234 | 
                  my $req_url = '/' . $c->req->url->path_query;  | 
            
            
              | 235 | 
               | 
            
            
              | 236 | 
                  my $borrower = Koha::Borrowers->find({userid => $req_username}); | 
            
            
              | 237 | 
               | 
            
            
              | 238 | 
                  #If the resource requires specific permissions, a strong authentication must be given.  | 
            
            
              | 239 | 
                  if ($opObj->{"x-koha-permission"}) { | 
            
            
              | 240 | 
                      #Does key authentication fail?  | 
            
            
              | 241 | 
                      _check_key_auth_api_key($c, $borrower);  | 
            
            
              | 242 | 
               | 
            
            
              | 243 | 
                      #Strong auth OK, Are there enough permissions?  | 
            
            
              | 244 | 
                      my ($failedPermission, $borrowerPermissions) = C4::Auth::haspermission($borrower, $opObj->{"x-koha-permission"}); | 
            
            
              | 245 | 
                      unless ($borrowerPermissions) { #Permissions are lacking. | 
            
            
              | 246 | 
                          my @permTokens = %$failedPermission if (ref $failedPermission eq 'HASH');  | 
            
            
              | 247 | 
                          my $failedPermissionString = (@permTokens) ? $permTokens[0].' => '.$permTokens[1] : "Permission unknown";  | 
            
            
              | 248 | 
                          Koha::Exception::NoPermission->throw(error => "No Koha permission to access this resource. Permission '$failedPermissionString' required.");  | 
            
            
              | 249 | 
                      }  | 
            
            
              | 250 | 
               | 
            
            
              | 251 | 
                      # Authentication succeeded, store authenticated user in stash  | 
            
            
              | 252 | 
                      $c->stash('user', $borrower); | 
            
            
              | 253 | 
                      return 1;  | 
            
            
              | 254 | 
                  }  | 
            
            
              | 255 | 
                  #No special permissions needed, try anon auth, and then strong auth.  | 
            
            
              | 256 | 
                  else { | 
            
            
              | 257 | 
                      # "Anonymous" mode  | 
            
            
              | 258 | 
                      if (_check_key_auth_anonymous($c)) { | 
            
            
              | 259 | 
                          return 'ANON';  | 
            
            
              | 260 | 
                      }  | 
            
            
              | 261 | 
                      _check_key_auth_api_key($c, $borrower);  | 
            
            
              | 262 | 
               | 
            
            
              | 263 | 
                      # Authentication succeeded, store authenticated user in stash  | 
            
            
              | 264 | 
                      $c->stash('user', $borrower); | 
            
            
              | 265 | 
                      return 1;  | 
            
            
              | 266 | 
                  }  | 
            
            
              | 267 | 
               | 
            
            
              | 268 | 
                  #If we reach this point there is something wrong. NEVER default to auth OK  | 
            
            
              | 269 | 
                  Koha::Exception::UnknownProgramState->throw(error => "Failure when authenticating. Unknown authentication state.");  | 
            
            
              | 270 | 
              }  | 
            
            
              | 271 | 
               | 
            
            
              | 272 | 
              =head _check_key_auth_anonymous  | 
            
            
              | 273 | 
               | 
            
            
              | 274 | 
              @PARAM1, Mojolicious::Controller  | 
            
            
              | 275 | 
              @RETURNS, Int, 2, if anonymous authentication  | 
            
            
              | 276 | 
                             undef, if no anonymous authentication.  | 
            
            
              | 277 | 
              =cut  | 
            
            
              | 278 | 
               | 
            
            
              | 279 | 
              sub _check_key_auth_anonymous { | 
            
            
              | 280 | 
                  my ($c) = @_;  | 
            
            
              | 281 | 
                  my $req_username = $c->req->headers->header('X-Koha-Username'); | 
            
            
              | 282 | 
                  my $req_timestamp = $c->req->headers->header('X-Koha-Timestamp'); | 
            
            
              | 283 | 
                  my $req_signature = $c->req->headers->header('X-Koha-Signature'); | 
            
            
              | 284 | 
               | 
            
            
              | 285 | 
                  return 2  | 
            
            
              | 286 | 
                    unless ( defined($req_username)  | 
            
            
              | 287 | 
                      or defined($req_timestamp)  | 
            
            
              | 288 | 
                      or defined($req_signature) );  | 
            
            
              | 289 | 
                  return undef;  | 
            
            
              | 290 | 
              }  | 
            
            
              | 291 | 
               | 
            
            
              | 292 | 
              =head _check_key_auth_api_key  | 
            
            
              | 293 | 
               | 
            
            
              | 294 | 
                  unless (_check_key_auth_api_key($c, $borrower)) { | 
            
            
              | 295 | 
                      return;  | 
            
            
              | 296 | 
                  }  | 
            
            
              | 297 | 
               | 
            
            
              | 298 | 
              @PARAM1 Mojolicious::Controller  | 
            
            
              | 299 | 
              @PARAM2 Koha::Borrower  | 
            
            
              | 300 | 
              @RETURNS Int, 1, if api authentication succeeded  | 
            
            
              | 301 | 
                             undef, authentication failed  | 
            
            
              | 302 | 
              @THROWS Koha::Exception::BadAuthenticationToken if borrower:  | 
            
            
              | 303 | 
                              has no API keys,  | 
            
            
              | 304 | 
                              signatures do not match,  | 
            
            
              | 305 | 
                              given timestamp is stale  | 
            
            
              | 306 | 
              =cut  | 
            
            
              | 307 | 
               | 
            
            
              | 308 | 
              sub _check_key_auth_api_key { | 
            
            
              | 309 | 
                  my ($c, $borrower) = @_;  | 
            
            
              | 310 | 
                  my $req_username = $c->req->headers->header('X-Koha-Username'); | 
            
            
              | 311 | 
                  my $req_timestamp = $c->req->headers->header('X-Koha-Timestamp'); | 
            
            
              | 312 | 
                  my $req_signature = $c->req->headers->header('X-Koha-Signature'); | 
            
            
              | 313 | 
                  my $req_method = $c->req->method;  | 
            
            
              | 314 | 
                  my $req_url = '/' . $c->req->url->path_query;  | 
            
            
              | 315 | 
               | 
            
            
              | 316 | 
                  my @apikeys = Koha::ApiKeys->search({ | 
            
            
              | 317 | 
                      borrowernumber => $borrower->borrowernumber,  | 
            
            
              | 318 | 
                      active => 1,  | 
            
            
              | 319 | 
                  });  | 
            
            
              | 320 | 
                  Koha::Exception::BadAuthenticationToken->throw(error => "User has no API keys") unless @apikeys;  | 
            
            
              | 321 | 
               | 
            
            
              | 322 | 
                  my $message = "$req_method $req_url $req_username $req_timestamp";  | 
            
            
              | 323 | 
                  my $signature = '';  | 
            
            
              | 324 | 
                  foreach my $apikey (@apikeys) { | 
            
            
              | 325 | 
                      $signature = hmac_sha256_hex($message, $apikey->api_key);  | 
            
            
              | 326 | 
               | 
            
            
              | 327 | 
                      last if $signature eq $req_signature;  | 
            
            
              | 328 | 
                  }  | 
            
            
              | 329 | 
               | 
            
            
              | 330 | 
                  unless ($signature eq $req_signature) { | 
            
            
              | 331 | 
                      Koha::Exception::BadAuthenticationToken->throw(error => "API key authentication failed");  | 
            
            
              | 332 | 
                  }  | 
            
            
              | 333 | 
               | 
            
            
              | 334 | 
                  my $api_timestamp = Koha::ApiTimestamps->find($borrower->borrowernumber);  | 
            
            
              | 335 | 
                  my $timestamp = $api_timestamp ? $api_timestamp->timestamp : 0;  | 
            
            
              | 336 | 
                  unless ($timestamp < $req_timestamp) { | 
            
            
              | 337 | 
                      Koha::Exception::BadAuthenticationToken->throw(error => "Bad X-Koha-Timestamp, expected '$timestamp'");  | 
            
            
              | 338 | 
                  }  | 
            
            
              | 339 | 
               | 
            
            
              | 340 | 
                  unless ($api_timestamp) { | 
            
            
              | 341 | 
                      $api_timestamp = new Koha::ApiTimestamp;  | 
            
            
              | 342 | 
                      $api_timestamp->borrowernumber($borrower->borrowernumber);  | 
            
            
              | 343 | 
                  }  | 
            
            
              | 344 | 
                  $api_timestamp->timestamp($req_timestamp);  | 
            
            
              | 345 | 
                  $api_timestamp->store;  | 
            
            
              | 346 | 
               | 
            
            
              | 347 | 
                  return 1;  | 
            
            
              | 348 | 
              }  | 
            
            
              | 349 | 
               | 
            
            
              | 350 | 
              return 1;  |