Bug 31378: Add API routes
authorAgustin Moyano <agustinmoyano@theke.io>
Thu, 18 Aug 2022 19:42:10 +0000 (16:42 -0300)
committerTomas Cohen Arazi <tomascohen@theke.io>
Tue, 8 Nov 2022 17:30:37 +0000 (14:30 -0300)
Signed-off-by: Lukasz Koszyk <lukasz.koszyk@kit.edu>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Koha/REST/V1.pm
Koha/REST/V1/OAuth/Client.pm [new file with mode: 0644]
api/v1/swagger/paths/public_oauth.yaml [new file with mode: 0644]
api/v1/swagger/swagger.yaml

index 13bbcfc..c0cd596 100644 (file)
@@ -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 (file)
index 0000000..36cca8a
--- /dev/null
@@ -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 <http://www.gnu.org/licenses>.
+
+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 (file)
index 0000000..dc88455
--- /dev/null
@@ -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"
index f3f1af4..bd07d47 100644 (file)
@@ -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":