From 5c8ab4f59083d56661af40c2f5f3a396f0913aa8 Mon Sep 17 00:00:00 2001 From: Agustin Moyano Date: Thu, 18 Aug 2022 16:42:10 -0300 Subject: [PATCH] Bug 31378: Add API routes Signed-off-by: Lukasz Koszyk Signed-off-by: Tomas Cohen Arazi Signed-off-by: Nick Clemens Signed-off-by: Martin Renvoize Signed-off-by: Tomas Cohen Arazi --- Koha/REST/V1.pm | 13 +++ Koha/REST/V1/OAuth/Client.pm | 135 +++++++++++++++++++++++++ api/v1/swagger/paths/public_oauth.yaml | 67 ++++++++++++ api/v1/swagger/swagger.yaml | 2 + 4 files changed, 217 insertions(+) create mode 100644 Koha/REST/V1/OAuth/Client.pm create mode 100644 api/v1/swagger/paths/public_oauth.yaml diff --git a/Koha/REST/V1.pm b/Koha/REST/V1.pm index 13bbcfcf20..c0cd59608a 100644 --- a/Koha/REST/V1.pm +++ b/Koha/REST/V1.pm @@ -21,10 +21,13 @@ use Mojo::Base 'Mojolicious'; use C4::Context; use Koha::Logger; +use Koha::Auth::Providers; +use Mojolicious::Plugin::OAuth2; use JSON::Validator::Schema::OpenAPIv2; use Try::Tiny qw( catch try ); +use JSON qw( decode_json ); =head1 NAME @@ -136,10 +139,20 @@ sub startup { }; }; + my $oauth_configuration = {}; + my $search_options = { protocol => [ "OIDC", "OAuth" ] }; + my $providers = Koha::Auth::Providers->search( $search_options ); + + while(my $provider = $providers->next) { + $oauth_configuration->{$provider->code} = decode_json($provider->config); + } + $self->plugin( 'Koha::REST::Plugin::Pagination' ); $self->plugin( 'Koha::REST::Plugin::Query' ); $self->plugin( 'Koha::REST::Plugin::Objects' ); $self->plugin( 'Koha::REST::Plugin::Exceptions' ); + $self->plugin( 'Koha::REST::Plugin::Auth' ); + $self->plugin( 'Mojolicious::Plugin::OAuth2' => $oauth_configuration ); } 1; diff --git a/Koha/REST/V1/OAuth/Client.pm b/Koha/REST/V1/OAuth/Client.pm new file mode 100644 index 0000000000..36cca8aac6 --- /dev/null +++ b/Koha/REST/V1/OAuth/Client.pm @@ -0,0 +1,135 @@ +package Koha::REST::V1::OAuth::Client; + +# 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, see . + +use Modern::Perl; + +use Koha::Auth::Client::OAuth; + +use Mojo::Base 'Mojolicious::Controller'; +use Mojo::URL; +use Scalar::Util qw(blessed); +use Try::Tiny; +use Koha::Logger; +use URI::Escape qw(uri_escape_utf8); + +=head1 NAME + +Koha::REST::V1::OAuth::Client - Controller library for handling OAuth2-related login attempts + +=head1 API + +=head2 Methods + +=head3 login + +Controller method handling login requests + +=cut + +sub login { + my $c = shift->openapi->valid_input or return; + + my $provider = $c->validation->param('provider_code'); + my $interface = $c->validation->param('interface'); + + my $logger = Koha::Logger->get({ interface => 'api' }); + + my $provider_config = $c->oauth2->providers->{$provider}; + + unless ( $provider_config ) { + return $c->render( + status => 404, + openapi => { + error => 'Object not found', + error_code => 'not_found', + } + ); + } + + unless ( $provider_config->{authorize_url} =~ /response_type=code/ ) { + my $authorize_url = Mojo::URL->new($provider_config->{authorize_url}); + $authorize_url->query->append(response_type => 'code'); + $provider_config->{authorize_url} = $authorize_url->to_string; + } + + my $uri; + + if ( $interface eq 'opac' ) { + if ( C4::Context->preference('OpacPublic') ) { + $uri = '/cgi-bin/koha/opac-user.pl'; + } else { + $uri = '/cgi-bin/koha/opac-main.pl'; + } + } else { + $uri = '/cgi-bin/koha/mainpage.pl'; + } + + return $c->oauth2->get_token_p($provider)->then( + sub { + return unless my $response = shift; + + my ( $patron, $mapped_data, $domain ) = Koha::Auth::Client::OAuth->new->get_user( + { provider => $provider, + data => $response, + interface => $interface, + config => $c->oauth2->providers->{$provider} + } + ); + + try { + # FIXME: We could check if the backend allows registering + if ( !$patron ) { + $patron = $c->auth->register( + { + data => $mapped_data, + domain => $domain, + interface => $interface + } + ); + } + + my ( $status, $cookie, $session_id ) = $c->auth->session($patron); + + $c->cookie( CGISESSID => $session_id, { path => "/" } ); + + $c->redirect_to($uri); + } catch { + my $error = $_; + $logger->error($error); + # TODO: Review behavior + if ( blessed $error ) { + if ( $error->isa('Koha::Exceptions::Auth::Unauthorized') ) { + $error = "$error"; + } + } + + $error = uri_escape_utf8($error); + + $c->redirect_to($uri."?auth_error=$error"); + }; + } + )->catch( + sub { + my $error = shift; + $logger->error($error); + $error = uri_escape_utf8($error); + $c->redirect_to($uri."?auth_error=$error"); + } + )->wait; +} + +1; diff --git a/api/v1/swagger/paths/public_oauth.yaml b/api/v1/swagger/paths/public_oauth.yaml new file mode 100644 index 0000000000..dc88455c1c --- /dev/null +++ b/api/v1/swagger/paths/public_oauth.yaml @@ -0,0 +1,67 @@ +"/public/oauth/login/{provider}/{interface}": + get: + x-mojo-to: OAuth::Client#login + operationId: loginOAuthClient + tags: + - oauth + summary: Login to OAuth provider + produces: + - application/json + parameters: + - name: provider + in: path + description: Name of OAuth provider + required: true + type: string + - name: interface + in: path + description: Name of the interface this login is for + required: true + type: string + - name: code + in: query + description: Code returned from OAuth server for Authorization Code grant + required: false + type: string + - name: state + in: query + description: An opaque value used by the client to maintain state between the request and callback. This is the callback part. + required: false + type: string + - name: scope + in: query + description: Scope returned by OAuth server + type: string + - name: prompt + in: query + description: Prompt returned by OAuth server + type: string + - name: authuser + in: query + description: Auth user returned by OAuth server + type: string + - name: error + in: query + description: OAuth error code + type: string + - name: error_description + in: query + description: OAuth error description + type: string + - name: error_uri + in: query + description: Web page with user friendly description of the error + type: string + responses: + "302": + description: User authorized + schema: + type: string + "400": + description: Bad Request + schema: + $ref: "../swagger.yaml#/definitions/error" + "403": + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index f3f1af4bc4..bd07d47908 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -269,6 +269,8 @@ paths: $ref: ./paths/libraries.yaml#/~1public~1libraries "/public/libraries/{library_id}": $ref: "./paths/libraries.yaml#/~1public~1libraries~1{library_id}" + "/public/oauth/login/{provider}/{interface}": + $ref: ./paths/public_oauth.yaml#/~1public~1oauth~1login~1{provider}~1{interface} "/public/patrons/{patron_id}/article_requests/{article_request_id}": $ref: "./paths/article_requests.yaml#/~1public~1patrons~1{patron_id}~1article_requests~1{article_request_id}" "/public/patrons/{patron_id}/guarantors/can_see_charges": -- 2.39.5