This introduces the concept of API keys for use in the new REST API.
A key is a string of 32 alphanumerical characters (32 is purely
arbitrary, it can be changed easily).
A user can have multiple keys (unlimited at the moment)
Keys can be generated automatically, and then we have the possibility to
delete or revoke each one individually.
Test plan:
1/ Go to staff interface
2/ Go to a borrower page
3/ In toolbar, click on More -> Manage API keys
4/ Click on "Generate new key" multiple times, check that they are
correctly displayed under the button, and they are active by default
5/ Revoke some keys, check that they are not active anymore
6/ Delete some keys, check that they disappear from table
7/ Go to opac interface, log in
8/ In your user account pages, you now have a new tab to the left "your
API keys". Click on it.
9/ Repeat steps 4-6
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
--- /dev/null
+package Koha::ApiKey;
+
+# 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::ApiKey - Koha API Key Object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+ return 'ApiKey';
+}
+
+1;
--- /dev/null
+package Koha::ApiKeys;
+
+# 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::ApiKeys - Koha API Keys Object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub type {
+ return 'ApiKey';
+}
+
+sub object_class {
+ return 'Koha::ApiKey';
+}
+
+1;
--- /dev/null
+use utf8;
+package Koha::Schema::Result::ApiKey;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Koha::Schema::Result::ApiKey
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<api_keys>
+
+=cut
+
+__PACKAGE__->table("api_keys");
+
+=head1 ACCESSORS
+
+=head2 borrowernumber
+
+ data_type: 'integer'
+ is_foreign_key: 1
+ is_nullable: 0
+
+=head2 api_key
+
+ data_type: 'varchar'
+ is_nullable: 0
+ size: 255
+
+=head2 active
+
+ data_type: 'integer'
+ default_value: 1
+ is_nullable: 1
+
+=cut
+
+__PACKAGE__->add_columns(
+ "borrowernumber",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+ "api_key",
+ { data_type => "varchar", is_nullable => 0, size => 255 },
+ "active",
+ { data_type => "integer", default_value => 1, is_nullable => 1 },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</borrowernumber>
+
+=item * L</api_key>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("borrowernumber", "api_key");
+
+=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 07:35:30
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dvujXVM5Vfu3SA2UfiVPtw
+
+
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
+1;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
+-- Table structure for table api_keys
+--
+
+DROP TABLE IF EXISTS api_keys;
+CREATE TABLE api_keys (
+ borrowernumber int(11) NOT NULL, -- foreign key to the borrowers table
+ api_key VARCHAR(255) NOT NULL, -- API key used for API authentication
+ active int(1) DEFAULT 1, -- 0 means this API key is revoked
+ PRIMARY KEY (borrowernumber, api_key),
+ CONSTRAINT api_keys_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`
--
[% ELSE %]
<li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to set permissions" id="patronflags" href="#">Set permissions</a></li>
[% END %]
+
[% IF CAN_user_borrowers_edit_borrowers && useDischarge %]
<li><a href="/cgi-bin/koha/members/discharge.pl?borrowernumber=[% patron.borrowernumber %]">Discharge</a></li>
[% END %]
[% IF CAN_user_borrowers_edit_borrowers %]
+
+ [% IF ( CAN_user_borrowers ) %]
+ <li><a id="apikeys" href="/cgi-bin/koha/members/apikeys.pl?borrowernumber=[% borrowernumber %]">Manage API keys</a></li>
+ [% ELSE %]
+ <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to manage API keys" id="apikeys" href="#">Manage API keys</a></li>
+ [% END %]
+
+ [% IF ( CAN_user_borrowers ) %]
[% IF ( NorwegianPatronDBEnable == 1 ) %]
<li><a id="deletepatronlocal" href="#">Delete local</a></li>
<li><a id="deletepatronremote" href="#">Delete remote</a></li>
--- /dev/null
+[% USE Koha %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha › Patrons [% IF ( searching ) %]› API Keys[% END %]</title>
+[% INCLUDE 'doc-head-close.inc' %]
+</head>
+<body id="pat_apikeys" class="pat">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'patron-search.inc' %]
+
+<div id="breadcrumbs">
+ <a href="/cgi-bin/koha/mainpage.pl">Home</a>
+ ›
+ <a href="/cgi-bin/koha/members/members-home.pl">Patrons</a>
+ ›
+ API Keys for [% INCLUDE 'patron-title.inc' %]
+</div>
+
+<div id="doc3" class="yui-t2">
+ <div id="bd">
+ <div id="yui-main">
+ <div class="yui-b">
+ [% INCLUDE 'members-toolbar.inc' %]
+
+ <h1>API keys for [% INCLUDE 'patron-title.inc' %]</h1>
+ <div>
+ <form action="/cgi-bin/koha/members/apikeys.pl" method="post">
+ <input type="hidden" name="borrowernumber" value="[% borrowernumber %]">
+ <input type="hidden" name="op" value="generate">
+ <input type="submit" value="Generate new key">
+ </form>
+ </div>
+ [% IF api_keys.size > 0 %]
+ <table>
+ <thead>
+ <tr>
+ <th>Key</th>
+ <th>Active</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH key IN api_keys %]
+ <tr>
+ <td>[% key.api_key %]</td>
+ <td>[% IF key.active %]Yes[% ELSE %]No[% END %]</td>
+ <td>
+ <form action="/cgi-bin/koha/members/apikeys.pl" method="post">
+ <input type="hidden" name="borrowernumber" value="[% borrowernumber %]">
+ <input type="hidden" name="key" value="[% key.api_key %]">
+ <input type="hidden" name="op" value="delete">
+ <input type="submit" value="Delete">
+ </form>
+ <form action="/cgi-bin/koha/members/apikeys.pl" method="post">
+ <input type="hidden" name="borrowernumber" value="[% borrowernumber %]">
+ <input type="hidden" name="key" value="[% key.api_key %]">
+ [% IF key.active %]
+ <input type="hidden" name="op" value="revoke">
+ <input type="submit" value="Revoke">
+ [% ELSE %]
+ <input type="hidden" name="op" value="activate">
+ <input type="submit" value="Activate">
+ [% END %]
+ </form>
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+ [% END %]
+ </div>
+ </div>
+ <div class="yui-b">
+ [% INCLUDE 'circ-menu.inc' %]
+ </div>
+ </div>
+[% INCLUDE 'intranet-bottom.inc' %]
+[% USE Koha %]
[% IF ( ( Koha.Preference( 'opacuserlogin' ) == 1 ) && loggedinusername ) %]
<div id="menu">
<h4><a href="#" class="menu-collapse-toggle">Your account menu</a></h4>
[% END %]
<a href="/cgi-bin/koha/opac-illrequests.pl">your interlibrary loan requests</a></li>
[% END %]
+
+ [% IF apikeysview %]
+ <li class="active">
+ [% ELSE %]
+ <li>
+ [% END %]
+ <a href="/cgi-bin/koha/opac-apikeys.pl">your API keys</a>
+ </li>
</ul>
</div>
[% END %]
--- /dev/null
+[% INCLUDE 'doc-head-open.inc' %]
+[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog › Your library home
+[% INCLUDE 'doc-head-close.inc' %]
+[% BLOCK cssinclude %][% END %]
+</head>
+[% INCLUDE 'bodytag.inc' bodyid='opac-user' bodyclass='scrollto' %]
+[% INCLUDE 'masthead.inc' %]
+
+<div class="main">
+ <ul class="breadcrumb">
+ <li><a href="/cgi-bin/koha/opac-main.pl">Home</a> <span class="divider">›</span></li>
+ <li>
+ <a href="/cgi-bin/koha/opac-user.pl">
+ [% INCLUDE 'patron-title.inc' category_type = borrower.category_type firstname = borrower.firstname surname = borrower.surname othernames = borrower.othernames %]
+ </a>
+ <span class="divider">›</span>
+ </li>
+ <li><a href="/cgi-bin/koha/opac-apikeys.pl">Your API keys</a></li>
+ </ul>
+
+ <div class="container-fluid">
+ <div class="row-fluid">
+ <div class="span2">
+ <div id="navigation">
+ [% INCLUDE 'navigation.inc' IsPatronPage = 1 %]
+ </div>
+ </div>
+ <div class="span10">
+ <div id="apikeys" class="maincontent">
+ <h1>Your API keys</h1>
+ <div>
+ <form action="/cgi-bin/koha/opac-apikeys.pl" method="post">
+ <input type="hidden" name="op" value="generate">
+ <input type="submit" value="Generate new key">
+ </form>
+ </div>
+ [% IF api_keys.size > 0 %]
+ <table class="table table-bordered table-striped">
+ <thead>
+ <tr>
+ <th>Key</th>
+ <th>Active</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH key IN api_keys %]
+ <tr>
+ <td>[% key.api_key %]</td>
+ <td>[% IF key.active %]Yes[% ELSE %]No[% END %]</td>
+ <td>
+ <form action="/cgi-bin/koha/opac-apikeys.pl" method="post" class="form-inline">
+ <input type="hidden" name="key" value="[% key.api_key %]">
+ <input type="hidden" name="op" value="delete">
+ <input type="submit" value="Delete">
+ </form>
+ <form action="/cgi-bin/koha/opac-apikeys.pl" method="post" class="form-inline">
+ <input type="hidden" name="key" value="[% key.api_key %]">
+ [% IF key.active %]
+ <input type="hidden" name="op" value="revoke">
+ <input type="submit" value="Revoke">
+ [% ELSE %]
+ <input type="hidden" name="op" value="activate">
+ <input type="submit" value="Activate">
+ [% END %]
+ </form>
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+ [% END %]
+ </div> <!-- /#apikeys -->
+ </div> <!-- /.span10 -->
+ </div> <!-- /.row-fluid -->
+ </div> <!-- /.container-fluid -->
+</div> <!-- /#main -->
+
+[% BLOCK jsinclude %][% END %]
+[% INCLUDE 'opac-bottom.inc' %]
--- /dev/null
+#!/usr/bin/env perl
+
+# Copyright 2015 BibLibre
+#
+# 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 2 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 CGI;
+use String::Random;
+
+use C4::Auth;
+use C4::Members;
+use C4::Output;
+use Koha::ApiKeys;
+use Koha::ApiKey;
+
+my $cgi = new CGI;
+
+my ($template, $loggedinuser, $cookie) = get_template_and_user({
+ template_name => 'members/apikeys.tt',
+ query => $cgi,
+ type => 'intranet',
+ authnotrequired => 0,
+ flagsrequired => {borrowers => 1},
+});
+
+my $borrowernumber = $cgi->param('borrowernumber');
+my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
+my $op = $cgi->param('op');
+
+if ($op) {
+ if ($op eq 'generate') {
+ my $apikey = new Koha::ApiKey;
+ $apikey->borrowernumber($borrowernumber);
+ $apikey->api_key(String::Random->new->randregex('[a-zA-Z0-9]{32}'));
+ $apikey->store;
+ print $cgi->redirect('/cgi-bin/koha/members/apikeys.pl?borrowernumber=' . $borrowernumber);
+ exit;
+ }
+
+ if ($op eq 'delete') {
+ my $key = $cgi->param('key');
+ my $api_key = Koha::ApiKeys->find({borrowernumber => $borrowernumber, api_key => $key});
+ if ($api_key) {
+ $api_key->delete;
+ }
+ print $cgi->redirect('/cgi-bin/koha/members/apikeys.pl?borrowernumber=' . $borrowernumber);
+ exit;
+ }
+
+ if ($op eq 'revoke') {
+ my $key = $cgi->param('key');
+ my $api_key = Koha::ApiKeys->find({borrowernumber => $borrowernumber, api_key => $key});
+ if ($api_key) {
+ $api_key->active(0);
+ $api_key->store;
+ }
+ print $cgi->redirect('/cgi-bin/koha/members/apikeys.pl?borrowernumber=' . $borrowernumber);
+ exit;
+ }
+
+ if ($op eq 'activate') {
+ my $key = $cgi->param('key');
+ my $api_key = Koha::ApiKeys->find({borrowernumber => $borrowernumber, api_key => $key});
+ if ($api_key) {
+ $api_key->active(1);
+ $api_key->store;
+ }
+ print $cgi->redirect('/cgi-bin/koha/members/apikeys.pl?borrowernumber=' . $borrowernumber);
+ exit;
+ }
+}
+
+my @api_keys = Koha::ApiKeys->search({borrowernumber => $borrowernumber});
+
+$template->param(
+ api_keys => \@api_keys,
+ borrower => $borrower,
+ borrowernumber => $borrowernumber,
+);
+
+output_html_with_http_headers $cgi, $cookie, $template->output;
--- /dev/null
+#!/usr/bin/env perl
+
+# Copyright 2015 BibLibre
+#
+# 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 2 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 CGI;
+use String::Random;
+
+use C4::Auth;
+use C4::Members;
+use C4::Output;
+use Koha::ApiKeys;
+use Koha::ApiKey;
+
+my $cgi = new CGI;
+
+my ($template, $loggedinuser, $cookie) = get_template_and_user({
+ template_name => 'opac-apikeys.tt',
+ query => $cgi,
+ type => 'opac',
+ authnotrequired => 0,
+ flagsrequired => {borrow => 1},
+});
+
+my $borrowernumber = $loggedinuser;
+my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
+my $op = $cgi->param('op');
+
+if ($op) {
+ if ($op eq 'generate') {
+ my $apikey = new Koha::ApiKey;
+ $apikey->borrowernumber($borrowernumber);
+ $apikey->api_key(String::Random->new->randregex('[a-zA-Z0-9]{32}'));
+ $apikey->store;
+ print $cgi->redirect('/cgi-bin/koha/opac-apikeys.pl');
+ exit;
+ }
+
+ if ($op eq 'delete') {
+ my $key = $cgi->param('key');
+ my $api_key = Koha::ApiKeys->find({borrowernumber => $borrowernumber, api_key => $key});
+ if ($api_key) {
+ $api_key->delete;
+ }
+ print $cgi->redirect('/cgi-bin/koha/opac-apikeys.pl');
+ exit;
+ }
+
+ if ($op eq 'revoke') {
+ my $key = $cgi->param('key');
+ my $api_key = Koha::ApiKeys->find({borrowernumber => $borrowernumber, api_key => $key});
+ if ($api_key) {
+ $api_key->active(0);
+ $api_key->store;
+ }
+ print $cgi->redirect('/cgi-bin/koha/opac-apikeys.pl');
+ exit;
+ }
+
+ if ($op eq 'activate') {
+ my $key = $cgi->param('key');
+ my $api_key = Koha::ApiKeys->find({borrowernumber => $borrowernumber, api_key => $key});
+ if ($api_key) {
+ $api_key->active(1);
+ $api_key->store;
+ }
+ print $cgi->redirect('/cgi-bin/koha/opac-apikeys.pl');
+ exit;
+ }
+}
+
+my @api_keys = Koha::ApiKeys->search({borrowernumber => $borrowernumber});
+
+$template->param(
+ apikeysview => 1,
+ api_keys => \@api_keys,
+ borrower => $borrower,
+ borrowernumber => $borrowernumber,
+);
+
+output_html_with_http_headers $cgi, $cookie, $template->output;