Bugzilla – Attachment 40624 Details for
Bug 13920
API authentication system - proposal
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 13920: 8. API Authentication, part 2: implement authentication in API
Bug-13920-8-API-Authentication-part-2-implement-au.patch (text/plain), 10.85 KB, created by
Julian Maurice
on 2015-06-25 09:33:20 UTC
(
hide
)
Description:
Bug 13920: 8. API Authentication, part 2: implement authentication in API
Filename:
MIME Type:
Creator:
Julian Maurice
Created:
2015-06-25 09:33:20 UTC
Size:
10.85 KB
patch
obsolete
>From b471a6918a53da21a8c1cce75586387bb4a8b1ef Mon Sep 17 00:00:00 2001 >From: Julian Maurice <julian.maurice@biblibre.com> >Date: Mon, 23 Mar 2015 20:19:12 +0100 >Subject: [PATCH] Bug 13920: 8. API Authentication, part 2: implement > authentication in API > >For authentication to succeed, the client have to send 3 custom HTTP >headers: > - X-Koha-Username: userid of borrower > - X-Koha-Timestamp: timestamp of the request > - X-Koha-Signature: signature of the request > >The signature is a HMAC-SHA256 hash of several elements of the request, >separated by spaces: > - HTTP method (uppercase) > - URL path and query string > - username > - timestamp of the request > >The server then tries to rebuild the signature with each user's API key. >If one matches the received X-Koha-Signature, then authentication is >almost OK. > >To avoid requests to be replayed, the last request's timestamp is stored >in database and the authentication succeeds only if the stored timestamp >is lesser than X-Koha-Timestamp. > >This patch implements server-side authentication (in Koha/REST/V1.pm) > >There is also an "anonymous" mode if X-Koha-* headers are not set. >Anonymous mode differ from authenticated mode in one thing: if user is >authenticated, the corresponding Koha::Borrower object is stored in >Mojolicious stash, so it can easily be retrieved by controllers. >Controllers then have the responsability of what to do if user is >authenticated or not. >--- > Koha/ApiTimestamp.pm | 46 +++++++++++++++++++ > Koha/ApiTimestamps.pm | 58 ++++++++++++++++++++++++ > Koha/REST/V1.pm | 63 ++++++++++++++++++++++++-- > Koha/Schema/Result/ApiTimestamp.pm | 81 ++++++++++++++++++++++++++++++++++ > installer/data/mysql/kohastructure.sql | 15 +++++++ > installer/data/mysql/updatedatabase.pl | 21 +++++++++ > 6 files changed, 281 insertions(+), 3 deletions(-) > create mode 100644 Koha/ApiTimestamp.pm > create mode 100644 Koha/ApiTimestamps.pm > create mode 100644 Koha/Schema/Result/ApiTimestamp.pm > >diff --git a/Koha/ApiTimestamp.pm b/Koha/ApiTimestamp.pm >new file mode 100644 >index 0000000..ed7d355 >--- /dev/null >+++ b/Koha/ApiTimestamp.pm >@@ -0,0 +1,46 @@ >+package Koha::ApiTimestamp; >+ >+# Copyright BibLibre 2015 >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 3 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along >+# with Koha; if not, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Carp; >+ >+use Koha::Database; >+ >+use base qw(Koha::Object); >+ >+=head1 NAME >+ >+Koha::ApiTimestamp - Koha API Timestamp Object class >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 type >+ >+=cut >+ >+sub type { >+ return 'ApiTimestamp'; >+} >+ >+1; >diff --git a/Koha/ApiTimestamps.pm b/Koha/ApiTimestamps.pm >new file mode 100644 >index 0000000..f5ba5dc >--- /dev/null >+++ b/Koha/ApiTimestamps.pm >@@ -0,0 +1,58 @@ >+package Koha::ApiTimestamps; >+ >+# Copyright BibLibre 2015 >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 3 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along >+# with Koha; if not, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Carp; >+ >+use Koha::Database; >+ >+use Koha::Borrower; >+ >+use base qw(Koha::Objects); >+ >+=head1 NAME >+ >+Koha::ApiTimestamps - Koha API Timestamps Object class >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 type >+ >+=cut >+ >+sub type { >+ return 'ApiTimestamp'; >+} >+ >+sub object_class { >+ return 'Koha::ApiTimestamp'; >+} >+ >+=head1 AUTHOR >+ >+Kyle M Hall <kyle@bywatersolutions.com> >+ >+=cut >+ >+1; >diff --git a/Koha/REST/V1.pm b/Koha/REST/V1.pm >index ea1810f..33a7ad9 100644 >--- a/Koha/REST/V1.pm >+++ b/Koha/REST/V1.pm >@@ -4,6 +4,16 @@ use Modern::Perl; > use Mojo::Base 'Mojolicious'; > use Mojo::Log; > >+use Digest::SHA qw(hmac_sha256_hex); >+ >+use Koha::Borrower; >+use Koha::Borrowers; >+use Koha::ApiKey; >+use Koha::ApiKeys; >+use Koha::ApiTimestamp; >+use Koha::ApiTimestamps; >+ >+ > =head startup > > Starts the Mojolicious application using whatever server is configured. >@@ -71,9 +81,56 @@ sub startup { > my $route = $self->routes->under->to( > cb => sub { > my $c = shift; >- my $user = $c->param('user'); >- # Do the authentication stuff here... >- $c->stash('user', $user); >+ my $req_username = $c->req->headers->header('X-Koha-Username'); >+ my $req_timestamp = $c->req->headers->header('X-Koha-Timestamp'); >+ my $req_signature = $c->req->headers->header('X-Koha-Signature'); >+ my $req_method = $c->req->method; >+ my $req_url = '/' . $c->req->url->path_query; >+ >+ # "Anonymous" mode >+ return 1 >+ unless ( defined($req_username) >+ or defined($req_timestamp) >+ or defined($req_signature) ); >+ >+ my $borrower = Koha::Borrowers->find({userid => $req_username}); >+ >+ my @apikeys = Koha::ApiKeys->search({ >+ borrowernumber => $borrower->borrowernumber, >+ active => 1, >+ }); >+ >+ my $message = "$req_method $req_url $req_username $req_timestamp"; >+ my $signature = ''; >+ foreach my $apikey (@apikeys) { >+ $signature = hmac_sha256_hex($message, $apikey->api_key); >+ >+ last if $signature eq $req_signature; >+ } >+ >+ unless ($signature eq $req_signature) { >+ $c->res->code(403); >+ $c->render(json => { error => "Authentication failed" }); >+ return; >+ } >+ >+ my $api_timestamp = Koha::ApiTimestamps->find($borrower->borrowernumber); >+ my $timestamp = $api_timestamp ? $api_timestamp->timestamp : 0; >+ unless ($timestamp < $req_timestamp) { >+ $c->res->code(403); >+ $c->render(json => { error => "Bad timestamp" }); >+ return; >+ } >+ >+ unless ($api_timestamp) { >+ $api_timestamp = new Koha::ApiTimestamp; >+ $api_timestamp->borrowernumber($borrower->borrowernumber); >+ } >+ $api_timestamp->timestamp($req_timestamp); >+ $api_timestamp->store; >+ >+ # Authentication succeeded, store authenticated user in stash >+ $c->stash('user', $borrower); > return 1; > } > ); >diff --git a/Koha/Schema/Result/ApiTimestamp.pm b/Koha/Schema/Result/ApiTimestamp.pm >new file mode 100644 >index 0000000..000ff5c >--- /dev/null >+++ b/Koha/Schema/Result/ApiTimestamp.pm >@@ -0,0 +1,81 @@ >+use utf8; >+package Koha::Schema::Result::ApiTimestamp; >+ >+# Created by DBIx::Class::Schema::Loader >+# DO NOT MODIFY THE FIRST PART OF THIS FILE >+ >+=head1 NAME >+ >+Koha::Schema::Result::ApiTimestamp >+ >+=cut >+ >+use strict; >+use warnings; >+ >+use base 'DBIx::Class::Core'; >+ >+=head1 TABLE: C<api_timestamps> >+ >+=cut >+ >+__PACKAGE__->table("api_timestamps"); >+ >+=head1 ACCESSORS >+ >+=head2 borrowernumber >+ >+ data_type: 'integer' >+ is_foreign_key: 1 >+ is_nullable: 0 >+ >+=head2 timestamp >+ >+ data_type: 'bigint' >+ is_nullable: 1 >+ >+=cut >+ >+__PACKAGE__->add_columns( >+ "borrowernumber", >+ { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, >+ "timestamp", >+ { data_type => "bigint", is_nullable => 1 }, >+); >+ >+=head1 PRIMARY KEY >+ >+=over 4 >+ >+=item * L</borrowernumber> >+ >+=back >+ >+=cut >+ >+__PACKAGE__->set_primary_key("borrowernumber"); >+ >+=head1 RELATIONS >+ >+=head2 borrowernumber >+ >+Type: belongs_to >+ >+Related object: L<Koha::Schema::Result::Borrower> >+ >+=cut >+ >+__PACKAGE__->belongs_to( >+ "borrowernumber", >+ "Koha::Schema::Result::Borrower", >+ { borrowernumber => "borrowernumber" }, >+ { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" }, >+); >+ >+ >+# Created by DBIx::Class::Schema::Loader v0.07025 @ 2015-03-24 08:27:29 >+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:FfKz14uY/UgVOWcF4T5J8A >+ >+ >+# You can replace this text with custom code or comments, and it will be preserved on regeneration >+1; >diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql >index a34590e..70effc7 100644 >--- a/installer/data/mysql/kohastructure.sql >+++ b/installer/data/mysql/kohastructure.sql >@@ -32,6 +32,21 @@ CREATE TABLE api_keys ( > ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; > > -- >+-- Table structure for table api_timestamps >+-- >+ >+DROP TABLE IF EXISTS api_timestamps; >+CREATE TABLE api_timestamps ( >+ borrowernumber int(11) NOT NULL, -- foreign key to the borrowers table >+ timestamp bigint, -- timestamp of the last request from this user >+ PRIMARY KEY (borrowernumber), >+ CONSTRAINT api_timestamps_fk_borrowernumber >+ FOREIGN KEY (borrowernumber) >+ REFERENCES borrowers (borrowernumber) >+ ON DELETE CASCADE ON UPDATE CASCADE >+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; >+ >+-- > -- Table structure for table `auth_header` > -- > >diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl >index 06c67cf..90707eb 100755 >--- a/installer/data/mysql/updatedatabase.pl >+++ b/installer/data/mysql/updatedatabase.pl >@@ -10663,6 +10663,27 @@ if(CheckVersion($DBversion)) { > SetVersion($DBversion); > } > >+$DBversion = "XXX"; >+if(CheckVersion($DBversion)) { >+ $dbh->do(q{ >+ DROP TABLE IF EXISTS api_timestamps; >+ }); >+ $dbh->do(q{ >+ CREATE TABLE api_timestamps ( >+ borrowernumber int(11) NOT NULL, >+ timestamp bigint, >+ PRIMARY KEY (borrowernumber), >+ CONSTRAINT api_timestamps_fk_borrowernumber >+ FOREIGN KEY (borrowernumber) >+ REFERENCES borrowers (borrowernumber) >+ ON DELETE CASCADE ON UPDATE CASCADE >+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; >+ }); >+ >+ print "Upgrade to $DBversion done (Bug 13920: Add API timestamps table)\n"; >+ SetVersion($DBversion); >+} >+ > > # DEVELOPER PROCESS, search for anything to execute in the db_update directory > # SEE bug 13068 >-- >1.9.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 13920
:
37286
|
37287
|
37288
|
40583
|
40590
|
40591
|
40592
|
40623
|
40624
|
40625
|
41165
|
41166
|
41195
|
41196
|
41257
|
41260
|
42041
|
42042
|
42555