From 3bb182d9f15a571c3434157efcb577ef8242aea7 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Thu, 4 Aug 2016 13:33:14 +0300 Subject: [PATCH] Bug 17004: Add API route to authenticate patron This patch adds a username/cardnumber and password authentication that returns a session. POST /auth/session (login) Required form data: - "password" - either "userid" or "cardnumber". To test: 1. Make sure you are logged out from Koha. 2. Make a POST request to http://yourlibrary/api/v1/auth/session with form data "userid" => and "password" => . 3. If your userid and password is correct, you should be returned with most basic patron data and your CGISESSID. 4. Also attempt with invalid login to get an error. --- Koha/REST/V1/Auth.pm | 73 +++++++++++++++++++++++++++++ api/v1/definitions/index.json | 1 + api/v1/definitions/session.json | 25 ++++++++++ api/v1/swagger.json | 50 ++++++++++++++++++++ t/db_dependent/api/v1/auth.t | 101 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 250 insertions(+) create mode 100644 Koha/REST/V1/Auth.pm create mode 100644 api/v1/definitions/session.json create mode 100644 t/db_dependent/api/v1/auth.t diff --git a/Koha/REST/V1/Auth.pm b/Koha/REST/V1/Auth.pm new file mode 100644 index 0000000..8f3e07a --- /dev/null +++ b/Koha/REST/V1/Auth.pm @@ -0,0 +1,73 @@ +package Koha::REST::V1::Auth; + +# Copyright 2016 Koha-Suomi +# +# 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 Mojo::Base 'Mojolicious::Controller'; + +use Koha::Patrons; +use C4::Auth; + +use CGI; + +sub login { + my ($c, $args, $cb) = @_; + + my $userid = $args->{userid} || $args->{cardnumber}; + my $password = $args->{password}; + my $patron; + + return $c->$cb({ error => "Either userid or cardnumber is required " + ."- neither given." }, 403) unless ($userid); + + my $cgi = CGI->new; + $cgi->param(userid => $userid); + $cgi->param(password => $password); + my ($status, $cookie, $sessionid) = C4::Auth::check_api_auth($cgi); + + return $c->$cb({ error => "Login failed." }, 401) if $status eq "failed"; + return $c->$cb({ error => "Session expired." }, 401) if $status eq "expired"; + return $c->$cb({ error => "Database is under maintenance." }, 401) if $status eq "maintenance"; + return $c->$cb({ error => "Login failed." }, 401) unless $status eq "ok"; + + $patron = Koha::Patrons->find({ userid => $userid }) unless $patron; + $patron = Koha::Patrons->find({ cardnumber => $userid }) unless $patron; + + my $session = _swaggerize_session($sessionid, $patron); + + $c->cookie(CGISESSID => $sessionid, { path => "/" }); + + return $c->$cb($session, 201); +} + +sub _swaggerize_session { + my ($sessionid, $patron) = @_; + + return unless ref($patron) eq 'Koha::Patron'; + + return { + borrowernumber => $patron->borrowernumber, + firstname => $patron->firstname, + surname => $patron->surname, + email => $patron->email, + sessionid => $sessionid, + }; +} + +1; diff --git a/api/v1/definitions/index.json b/api/v1/definitions/index.json index 3f69544..9b75086 100644 --- a/api/v1/definitions/index.json +++ b/api/v1/definitions/index.json @@ -1,4 +1,5 @@ { + "session": { "$ref": "session.json" }, "patron": { "$ref": "patron.json" }, "holds": { "$ref": "holds.json" }, "hold": { "$ref": "hold.json" }, diff --git a/api/v1/definitions/session.json b/api/v1/definitions/session.json new file mode 100644 index 0000000..f569f30 --- /dev/null +++ b/api/v1/definitions/session.json @@ -0,0 +1,25 @@ +{ + "type": "object", + "properties": { + "borrowernumber": { + "type": "string", + "description": "internally assigned user identifier" + }, + "email": { + "type": ["string", "null"], + "description": "primary email address for patron's primary address" + }, + "surname": { + "type": "string", + "description": "patron's last name" + }, + "firstname": { + "type": ["string", "null"], + "description": "patron's first name" + }, + "sessionid": { + "description": "Koha Session identifier", + "type": "string" + } + } +} diff --git a/api/v1/swagger.json b/api/v1/swagger.json index b3f30f8..256aee6 100644 --- a/api/v1/swagger.json +++ b/api/v1/swagger.json @@ -14,6 +14,36 @@ }, "basePath": "/api/v1", "paths": { + "/auth/session": { + "post": { + "operationId": "loginAuth", + "tags": ["auth"], + "summary": "Login to Koha and get a session cookie", + "description": "Makes a 'normal' username + password login to Koha, and returns the sessionid you need put to the CGISESSID-cookie. Koha uses this cookie to track a session.\nBe aware that the authenticated session most probably is IP-locked so authenticating from one IP and passing the session to another wont work.", + "parameters": [ + { "$ref": "#/parameters/cardnumberPostParam" }, + { "$ref": "#/parameters/useridPostParam" }, + { "$ref": "#/parameters/passwordPostParam" } + ], + "produces": [ + "application/json" + ], + "responses": { + "201": { + "description": "A borrower with SSO-relevant fields", + "schema": { + "$ref": "#/definitions/session" + } + }, + "401": { + "description": "Bad username/cardnumber and/or password", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/patrons": { "get": { "operationId": "listPatrons", @@ -345,12 +375,32 @@ "required": true, "type": "integer" }, + "cardnumberPostParam": { + "name": "cardnumber", + "in": "formData", + "description": "Borrower's card's barcode/identifier", + "required": false, + "type": "string" + }, "holdIdPathParam": { "name": "reserve_id", "in": "path", "description": "Internal hold identifier", "required": true, "type": "integer" + }, + "passwordPostParam": { + "name": "password", + "in": "formData", + "required": true, + "type": "string" + }, + "useridPostParam": { + "name": "userid", + "in": "formData", + "description": "The userid of the Borrower, unique value", + "required": false, + "type": "string" } } } diff --git a/t/db_dependent/api/v1/auth.t b/t/db_dependent/api/v1/auth.t new file mode 100644 index 0000000..33055c2 --- /dev/null +++ b/t/db_dependent/api/v1/auth.t @@ -0,0 +1,101 @@ +#!/usr/bin/env perl + +# Copyright 2016 Koha-Suomi +# +# 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 Test::More tests => 20; +use Test::Mojo; + +use t::lib::TestBuilder; + +use C4::Context; +use Koha::AuthUtils; + +my $builder = t::lib::TestBuilder->new(); + +my $dbh = C4::Context->dbh; +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; + +$ENV{REMOTE_ADDR} = '127.0.0.1'; +my $t = Test::Mojo->new('Koha::REST::V1'); + +my $categorycode = $builder->build({ source => 'Category' })->{ categorycode }; +my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode }; +my $password = "2anxious? if someone finds out"; + +my $borrower = $builder->build({ + source => 'Borrower', + value => { + branchcode => $branchcode, + categorycode => $categorycode, + password => Koha::AuthUtils::hash_password($password), + } +}); + +my $auth_by_userid = { + userid => $borrower->{userid}, + password => $password, +}; +my $auth_by_cardnumber = { + cardnumber => $borrower->{cardnumber}, + password => $password, +}; +my $invalid_login = { + userid => $borrower->{userid}, + password => "please let me in", +}; +my $invalid_login2 = { + cardnumber => $borrower->{cardnumber}, + password => "my password is password, don't tell anyone", +}; + +my $tx = $t->ua->build_tx(POST => '/api/v1/auth/session' => form => $auth_by_userid); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(201) + ->json_is('/firstname', $borrower->{firstname}) + ->json_is('/surname', $borrower->{surname}) + ->json_is('/borrowernumber', $borrower->{borrowernumber}) + ->json_is('/email', $borrower->{email}) + ->json_has('/sessionid'); + +$tx = $t->ua->build_tx(POST => '/api/v1/auth/session' => form => $auth_by_cardnumber); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(201) + ->json_is('/firstname', $borrower->{firstname}) + ->json_is('/surname', $borrower->{surname}) + ->json_is('/borrowernumber', $borrower->{borrowernumber}) + ->json_is('/email', $borrower->{email}) + ->json_has('/sessionid'); + +$tx = $t->ua->build_tx(POST => '/api/v1/auth/session' => form => $invalid_login); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(401) + ->json_is('/error', "Login failed."); + +$tx = $t->ua->build_tx(POST => '/api/v1/auth/session' => form => $invalid_login2); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(401) + ->json_is('/error', "Login failed."); + +$dbh->rollback; -- 1.9.1