From a9d9599ffbff8929e2ed99183e5390b54386908a 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. 5. Run tests at b/t/db_dependent/api/v1/auth.t Signed-off-by: Jiri Kozlovsky Wow, brilliant work! Thanks for that, it works as expected! --- 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; -- 2.1.4