Bug 31378: Add authentication provider endpoints
authorTomas Cohen Arazi <tomascohen@theke.io>
Tue, 30 Aug 2022 13:33:14 +0000 (10:33 -0300)
committerTomas Cohen Arazi <tomascohen@theke.io>
Tue, 8 Nov 2022 17:31:20 +0000 (14:31 -0300)
This patch adds routes for handling authentication providers to the REST
API.

To test:
1. Apply this patch
2. Run:
   $ kshell
  k$ prove t/db_dependent/api/v1/auth_providers.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lukasz Koszyk <lukasz.koszyk@kit.edu>
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>
12 files changed:
Koha/Auth/Provider.pm
Koha/Auth/Provider/OAuth.pm [new file with mode: 0644]
Koha/Auth/Provider/OIDC.pm [new file with mode: 0644]
Koha/REST/V1/Auth/Provider/Domains.pm [new file with mode: 0644]
Koha/REST/V1/Auth/Providers.pm [new file with mode: 0644]
api/v1/swagger/definitions/auth_provider.yaml [new file with mode: 0644]
api/v1/swagger/definitions/auth_provider_domain.yaml [new file with mode: 0644]
api/v1/swagger/paths/auth.yaml
api/v1/swagger/paths/oauth.yaml
api/v1/swagger/paths/public_oauth.yaml
api/v1/swagger/swagger.yaml
t/db_dependent/Koha/Auth/Provider.t

index e8de84a..7a652de 100644 (file)
@@ -97,22 +97,11 @@ This method stores the passed config in JSON format.
 sub set_config {
     my ($self, $config) = @_;
 
-    my @mandatory;
-
-    if ( $self->protocol eq 'OIDC' ) {
-        @mandatory = qw(key secret well_known_url);
-    }
-    elsif ( $self->protocol eq 'OAuth' ) {
-        @mandatory = qw(key secret authorize_url token_url);
-    }
-    else {
-        Koha::Exception->throw( 'Unsupported protocol ' . $self->protocol );
-    }
+    my @mandatory = $self->mandatory_config_attributes;
 
     for my $param (@mandatory) {
         unless ( defined( $config->{$param} ) ) {
-            Koha::Exceptions::MissingParameter->throw(
-                error => "The $param parameter is mandatory" );
+            Koha::Exceptions::MissingParameter->throw( parameter => $param );
         }
     }
 
@@ -167,8 +156,51 @@ sub set_mapping {
     return $self;
 }
 
+=head3 upgrade_class
+
+    my $upgraded_object = $provider->upgrade_class
+
+Returns a new instance of the object, with the right class.
+
+=cut
+
+sub upgrade_class {
+    my ( $self ) = @_;
+    my $protocol = $self->protocol;
+
+    my $class = $self->protocol_to_class_mapping->{$protocol};
+
+    Koha::Exception->throw($protocol . ' is not a valid protocol')
+        unless $class;
+
+    eval "require $class";
+    return $class->_new_from_dbic( $self->_result );
+}
+
 =head2 Internal methods
 
+=head3 to_api
+
+    my $json = $provider->to_api;
+
+Overloaded method that returns a JSON representation of the Koha::Auth::Provider object,
+suitable for API output.
+
+=cut
+
+sub to_api {
+    my ( $self, $params ) = @_;
+
+    my $config  = $self->get_config;
+    my $mapping = $self->get_mapping;
+
+    my $json = $self->SUPER::to_api($params);
+    $json->{config}  = $config;
+    $json->{mapping} = $mapping;
+
+    return $json;
+}
+
 =head3 _type
 
 =cut
@@ -177,4 +209,31 @@ sub _type {
     return 'AuthProvider';
 }
 
+=head3 protocol_to_class_mapping
+
+    my $mapping = Koha::Auth::Provider::protocol_to_class_mapping
+
+Internal method that returns a mapping between I<protocol> codes and
+implementing I<classes>. To be used by B<upgrade_class>.
+
+=cut
+
+sub protocol_to_class_mapping {
+    return {
+        OAuth => 'Koha::Auth::Provider::OAuth',
+        OIDC  => 'Koha::Auth::Provider::OIDC',
+    };
+}
+
+=head3 mandatory_config_attributes
+
+Stub method for raising exceptions on invalid protocols.
+
+=cut
+
+sub mandatory_config_attributes {
+    my ($self) = @_;
+    Koha::Exception->throw("This method needs to be subclassed");
+}
+
 1;
diff --git a/Koha/Auth/Provider/OAuth.pm b/Koha/Auth/Provider/OAuth.pm
new file mode 100644 (file)
index 0000000..b77f4af
--- /dev/null
@@ -0,0 +1,65 @@
+package Koha::Auth::Provider::OAuth;
+
+# Copyright Theke Solutions 2022
+#
+# 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 base qw(Koha::Auth::Provider);
+
+=head1 NAME
+
+Koha::Auth::Provider::OAuth - Koha Auth Provider Object class
+
+=head1 API
+
+=head2 Class methods
+
+=head3 new
+
+    my $oauth = Koha::Auth::Provider::OAuth->new( \%{params} );
+
+Overloaded class to create a new OAuth provider.
+
+=cut
+
+sub new {
+    my ( $class, $params ) = @_;
+
+    $params->{protocol} = 'OAuth';
+
+    return $class->SUPER::new($params);
+}
+
+=head2 Internal methods
+
+=head3 mandatory_config_attributes
+
+Returns a list of the mandatory config entries for the protocol.
+
+=cut
+
+sub mandatory_config_attributes {
+    return qw(
+      key
+      secret
+      authorize_url
+      token_url
+    );
+}
+
+1;
diff --git a/Koha/Auth/Provider/OIDC.pm b/Koha/Auth/Provider/OIDC.pm
new file mode 100644 (file)
index 0000000..5945555
--- /dev/null
@@ -0,0 +1,64 @@
+package Koha::Auth::Provider::OIDC;
+
+# Copyright Theke Solutions 2022
+#
+# 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 base qw(Koha::Auth::Provider);
+
+=head1 NAME
+
+Koha::Auth::Provider::OIDC - Koha Auth Provider Object class
+
+=head1 API
+
+=head2 Class methods
+
+=head3 new
+
+    my $oidc = Koha::Auth::Provider::OIDC->new( \%{params} );
+
+Overloaded class to create a new OIDC provider.
+
+=cut
+
+sub new {
+    my ( $class, $params ) = @_;
+
+    $params->{protocol} = 'OIDC';
+
+    return $class->SUPER::new($params);
+}
+
+=head2 Internal methods
+
+=head3 mandatory_config_attributes
+
+Returns a list of the mandatory config entries for the protocol.
+
+=cut
+
+sub mandatory_config_attributes {
+    return qw(
+      key
+      secret
+      well_known_url
+    );
+}
+
+1;
diff --git a/Koha/REST/V1/Auth/Provider/Domains.pm b/Koha/REST/V1/Auth/Provider/Domains.pm
new file mode 100644 (file)
index 0000000..f53954a
--- /dev/null
@@ -0,0 +1,233 @@
+package Koha::REST::V1::Auth::Provider::Domains;
+
+# 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 Mojo::Base 'Mojolicious::Controller';
+
+use Koha::Auth::Provider::Domains;
+use Koha::Auth::Providers;
+
+use Koha::Database;
+
+use Scalar::Util qw(blessed);
+use Try::Tiny;
+
+=head1 NAME
+
+Koha::REST::V1::Auth::Provider::Domains - Controller library for handling
+authentication provider domains routes.
+
+=head2 Operations
+
+=head3 list
+
+Controller method for listing authentication provider domains.
+
+=cut
+
+sub list {
+    my $c = shift->openapi->valid_input or return;
+
+    return try {
+        my $auth_provider_id = $c->validation->param('auth_provider_id');
+        my $provider         = Koha::Auth::Providers->find($auth_provider_id);
+
+        unless ($provider) {
+            return $c->render(
+                status  => 404,
+                openapi => {
+                    error      => 'Object not found',
+                    error_code => 'not_found',
+                }
+            );
+        }
+
+        my $domains_rs = $provider->domains;
+        return $c->render(
+            status  => 200,
+            openapi => $c->objects->search($domains_rs)
+        );
+    } catch {
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 get
+
+Controller method for retrieving an authentication provider domain.
+
+=cut
+
+sub get {
+    my $c = shift->openapi->valid_input or return;
+
+    return try {
+
+        my $auth_provider_id = $c->validation->param('auth_provider_id');
+        my $provider         = Koha::Auth::Providers->find($auth_provider_id);
+
+        unless ($provider) {
+            return $c->render(
+                status  => 404,
+                openapi => {
+                    error      => 'Object not found',
+                    error_code => 'not_found',
+                }
+            );
+        }
+
+        my $domains_rs = $provider->domains;
+
+        my $auth_provider_domain_id = $c->validation->param('auth_provider_domain_id');
+        my $domain                  = $c->objects->find( $domains_rs, $auth_provider_domain_id );
+
+        unless ($domain) {
+            return $c->render(
+                status  => 404,
+                openapi => {
+                    error      => 'Object not found',
+                    error_code => 'not_found',
+                }
+            );
+        }
+
+        return $c->render( status => 200, openapi => $domain );
+    } catch {
+        $c->unhandled_exception($_);
+    }
+}
+
+=head3 add
+
+Controller method for adding an authentication provider.
+
+=cut
+
+sub add {
+    my $c = shift->openapi->valid_input or return;
+
+    return try {
+
+        Koha::Database->new->schema->txn_do(
+            sub {
+                my $domain = Koha::Auth::Provider::Domain->new_from_api( $c->validation->param('body') );
+                $domain->store;
+
+                $c->res->headers->location( $c->req->url->to_string . '/' . $domain->id );
+                return $c->render(
+                    status  => 201,
+                    openapi => $domain->to_api
+                );
+            }
+        );
+    } catch {
+        if ( blessed($_) and $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
+            return $c->render(
+                status  => 404,
+                openapi => {
+                    error      => 'Object not found',
+                    error_code => 'not_found',
+                }
+            );
+        }
+
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 update
+
+Controller method for updating an authentication provider domain.
+
+=cut
+
+sub update {
+    my $c = shift->openapi->valid_input or return;
+
+    my $auth_provider_id        = $c->validation->param('auth_provider_id');
+    my $auth_provider_domain_id = $c->validation->param('auth_provider_domain_id');
+
+    my $domain = Koha::Auth::Provider::Domains->find(
+        { auth_provider_id => $auth_provider_id, auth_provider_domain_id => $auth_provider_domain_id } );
+
+    unless ($domain) {
+        return $c->render(
+            status  => 404,
+            openapi => {
+                error      => 'Object not found',
+                error_code => 'not_found',
+            }
+        );
+    }
+
+    return try {
+
+        Koha::Database->new->schema->txn_do(
+            sub {
+
+                $domain->set_from_api( $c->validation->param('body') );
+                $domain->store->discard_changes;
+
+                return $c->render(
+                    status  => 200,
+                    openapi => $domain->to_api
+                );
+            }
+        );
+    } catch {
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 delete
+
+Controller method for deleting an authentication provider.
+
+=cut
+
+sub delete {
+    my $c = shift->openapi->valid_input or return;
+
+    my $auth_provider_id        = $c->validation->param('auth_provider_id');
+    my $auth_provider_domain_id = $c->validation->param('auth_provider_domain_id');
+
+    my $domain = Koha::Auth::Provider::Domains->find(
+        { auth_provider_id => $auth_provider_id, auth_provider_domain_id => $auth_provider_domain_id } );
+
+    unless ($domain) {
+        return $c->render(
+            status  => 404,
+            openapi => {
+                error      => 'Object not found',
+                error_code => 'not_found',
+            }
+        );
+    }
+
+    return try {
+        $domain->delete;
+        return $c->render(
+            status  => 204,
+            openapi => q{}
+        );
+    } catch {
+        $c->unhandled_exception($_);
+    };
+}
+
+1;
diff --git a/Koha/REST/V1/Auth/Providers.pm b/Koha/REST/V1/Auth/Providers.pm
new file mode 100644 (file)
index 0000000..c31b258
--- /dev/null
@@ -0,0 +1,237 @@
+package Koha::REST::V1::Auth::Providers;
+
+# 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 Mojo::Base 'Mojolicious::Controller';
+
+use Koha::Auth::Provider::OAuth;
+use Koha::Auth::Provider::OIDC;
+use Koha::Auth::Providers;
+
+use Koha::Database;
+
+use Scalar::Util qw(blessed);
+use Try::Tiny;
+
+=head1 NAME
+
+Koha::REST::V1::Auth::Providers - Controller library for handling
+authentication providers routes.
+
+=head2 Operations
+
+=head3 list
+
+Controller method for listing authentication providers.
+
+=cut
+
+sub list {
+    my $c = shift->openapi->valid_input or return;
+
+    return try {
+        my $providers_rs = Koha::Auth::Providers->new;
+        return $c->render(
+            status  => 200,
+            openapi => $c->objects->search($providers_rs)
+        );
+    } catch {
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 get
+
+Controller method for retrieving an authentication provider.
+
+=cut
+
+sub get {
+    my $c = shift->openapi->valid_input or return;
+
+    return try {
+
+        my $auth_provider_id = $c->validation->param('auth_provider_id');
+        my $provider = $c->objects->find( Koha::Auth::Providers->new, $auth_provider_id );
+
+        unless ( $provider ) {
+            return $c->render(
+                status  => 404,
+                openapi => {
+                    error      => 'Object not found',
+                    error_code => 'not_found',
+                }
+            );
+        }
+
+        return $c->render( status => 200, openapi => $provider );
+    }
+    catch {
+        $c->unhandled_exception($_);
+    }
+}
+
+=head3 add
+
+Controller method for adding an authentication provider.
+
+=cut
+
+sub add {
+    my $c = shift->openapi->valid_input or return;
+
+    return try {
+
+        Koha::Database->new->schema->txn_do(
+            sub {
+
+                my $body = $c->validation->param('body');
+
+                my $config   = delete $body->{config};
+                my $mapping  = delete $body->{mapping};
+                my $protocol = delete $body->{protocol};
+
+                my $class = Koha::Auth::Provider::protocol_to_class_mapping->{$protocol};
+
+                my $provider = $class->new_from_api( $body );
+                $provider->store;
+
+                $provider->set_config( $config );
+                $provider->set_mapping( $mapping );
+
+                $c->res->headers->location( $c->req->url->to_string . '/' . $provider->auth_provider_id );
+                return $c->render(
+                    status  => 201,
+                    openapi => $provider->to_api
+                );
+            }
+        );
+    }
+    catch {
+        if ( blessed($_) ) {
+            if ( $_->isa('Koha::Exceptions::MissingParameter') ) {
+                return $c->render(
+                    status  => 400,
+                    openapi => {
+                        error      => "Missing parameter config." . $_->parameter,
+                        error_code => 'missing_parameter'
+                    }
+                );
+            }
+        }
+
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 update
+
+Controller method for updating an authentication provider.
+
+=cut
+
+sub update {
+    my $c = shift->openapi->valid_input or return;
+
+    my $auth_provider_id = $c->validation->param('auth_provider_id');
+    my $provider = Koha::Auth::Providers->find( $auth_provider_id );
+
+    unless ( $provider ) {
+        return $c->render(
+            status  => 404,
+            openapi => {
+                error      => 'Object not found',
+                error_code => 'not_found',
+            }
+        );
+    }
+
+    return try {
+
+        Koha::Database->new->schema->txn_do(
+            sub {
+
+                my $body = $c->validation->param('body');
+
+                my $config   = delete $body->{config};
+                my $mapping  = delete $body->{mapping};
+
+                $provider = $provider->set_from_api( $body )->upgrade_class;
+
+                $provider->set_config( $config );
+                $provider->set_mapping( $mapping );
+                # set_config and set_mapping already called store()
+                $provider->discard_changes;
+
+                return $c->render(
+                    status  => 200,
+                    openapi => $provider->to_api
+                );
+            }
+        );
+    }
+    catch {
+        if ( blessed($_) ) {
+            if ( $_->isa('Koha::Exceptions::MissingParameter') ) {
+                return $c->render(
+                    status  => 400,
+                    openapi => {
+                        error      => "Missing parameter config." . $_->parameter,
+                        error_code => 'missing_parameter'
+                    }
+                );
+            }
+        }
+
+        $c->unhandled_exception($_);
+    };
+}
+
+=head3 delete
+
+Controller method for deleting an authentication provider.
+
+=cut
+
+sub delete {
+    my $c = shift->openapi->valid_input or return;
+
+    my $provider = Koha::Auth::Providers->find( $c->validation->param('auth_provider_id') );
+    unless ( $provider ) {
+        return $c->render(
+            status  => 404,
+            openapi => {
+                error      => 'Object not found',
+                error_code => 'not_found',
+            }
+        );
+    }
+
+    return try {
+        $provider->delete;
+        return $c->render(
+            status  => 204,
+            openapi => q{}
+        );
+    }
+    catch {
+        $c->unhandled_exception($_);
+    };
+}
+
+1;
diff --git a/api/v1/swagger/definitions/auth_provider.yaml b/api/v1/swagger/definitions/auth_provider.yaml
new file mode 100644 (file)
index 0000000..93e587b
--- /dev/null
@@ -0,0 +1,49 @@
+---
+type: object
+properties:
+  auth_provider_id:
+    type: integer
+    description: Internally assigned authentication provider identifier
+    readOnly: true
+  code:
+    description: Authentication provider code
+    type: string
+  description:
+    description: User-oriented description for the provider
+    type: string
+  protocol:
+    description: Authentication protocol
+    type: string
+    enum:
+      - OAuth
+      - OIDC
+      - CAS (not implemented)
+      - LDAP (not implemented)
+  mapping:
+    description: Attribute mapping
+    type:
+      - object
+      - "null"
+  matchpoint:
+    description: Patron attribute that will be used to match
+    type: string
+    enum:
+      - email
+      - userid
+      - cardnumber
+  config:
+    description: Configuration
+    type: object
+  icon_url:
+    description: Icon url
+    type: string
+  domains:
+    description: Configured domains for the authentication provider
+    type:
+      - array
+      - "null"
+additionalProperties: false
+required:
+  - config
+  - code
+  - protocol
diff --git a/api/v1/swagger/definitions/auth_provider_domain.yaml b/api/v1/swagger/definitions/auth_provider_domain.yaml
new file mode 100644 (file)
index 0000000..bc9f60a
--- /dev/null
@@ -0,0 +1,48 @@
+---
+type: object
+properties:
+  auth_provider_domain_id:
+    type: integer
+    description: Internally assigned authentication provider domain identifier
+    readOnly: true
+  auth_provider_id:
+    type: integer
+    description: Internally assigned authentication provider identifier
+  domain:
+    description: Matching domain ('*' used as wildcard)
+    type:
+      - string
+      - "null"
+  auto_register:
+    description: If patrons will be generated on login if required
+    type: boolean
+  update_on_auth:
+    description: If patron data is updated on login
+    type: boolean
+  default_library_id:
+    description: Internal identifier for the default library to be assigned to the new patrons
+    type:
+      - string
+      - "null"
+  default_category_id:
+    description: Internal identifier for the default patron's category
+    type:
+      - string
+      - "null"
+  allow_opac:
+    description: If this domain can be used for OPAC login
+    type: boolean
+  allow_staff:
+    description: If this domain can be used for staff login
+    type: boolean
+additionalProperties: false
+required:
+  - auth_provider_domain_id
+  - auth_provider_id
+  - domain
+  - auto_register
+  - update_on_auth
+  - default_library_id
+  - default_category_id
+  - allow_opac
+  - allow_staff
index 40c886a..ae9c655 100644 (file)
     x-koha-authorization:
       permissions:
         catalogue: "1"
+/auth/providers:
+  get:
+    x-mojo-to: Auth::Providers#list
+    operationId: listAuthProviders
+    tags:
+      - auth_providers
+    summary: List configured authentication providers
+    parameters:
+      - $ref: ../swagger.yaml#/parameters/match
+      - $ref: ../swagger.yaml#/parameters/order_by
+      - $ref: ../swagger.yaml#/parameters/page
+      - $ref: ../swagger.yaml#/parameters/per_page
+      - $ref: ../swagger.yaml#/parameters/q_param
+      - $ref: ../swagger.yaml#/parameters/q_body
+      - $ref: ../swagger.yaml#/parameters/q_header
+      - $ref: ../swagger.yaml#/parameters/request_id_header
+      - name: x-koha-embed
+        in: header
+        required: false
+        description: Embed list sent as a request header
+        type: array
+        items:
+          type: string
+          enum:
+            - domains
+        collectionFormat: csv
+    produces:
+      - application/json
+    responses:
+      "200":
+        description: A list of authentication providers
+        schema:
+          type: array
+          items:
+            $ref: ../swagger.yaml#/definitions/auth_provider
+      "400":
+        description: Bad Request
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "403":
+        description: Access forbidden
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
+  post:
+    x-mojo-to: Auth::Providers#add
+    operationId: addAuthProvider
+    tags:
+      - auth_providers
+    summary: Add a new authentication provider
+    parameters:
+      - name: body
+        in: body
+        description: |
+          A JSON object containing OAuth provider parameters.
+
+          The `config` object required attributes depends on the chosen `protocol`
+
+          ## OAuth
+
+          Requires:
+
+          * key
+          * secret
+          * authorize_url
+          * token_url
+
+          ## OIDC
+
+          Requires:
+
+          * key
+          * secret
+          * well_known_url
+        required: true
+        schema:
+          $ref: ../swagger.yaml#/definitions/auth_provider
+    produces:
+      - application/json
+    responses:
+      "201":
+        description: The generated authentication provider
+        schema:
+          $ref: ../swagger.yaml#/definitions/auth_provider
+      "400":
+        description: Bad Request
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "403":
+        description: Access forbidden
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
+"/auth/providers/{auth_provider_id}":
+  get:
+    x-mojo-to: Auth::Providers#get
+    operationId: getAuthProvider
+    tags:
+      - auth_providers
+    summary: Get authentication provider
+    parameters:
+      - $ref: ../swagger.yaml#/parameters/auth_provider_id_pp
+      - name: x-koha-embed
+        in: header
+        required: false
+        description: Embed list sent as a request header
+        type: array
+        items:
+          type: string
+          enum:
+            - domains
+        collectionFormat: csv
+    produces:
+      - application/json
+    responses:
+      "200":
+        description: An authentication provider
+        schema:
+          $ref: ../swagger.yaml#/definitions/auth_provider
+      "404":
+        description: Object not found
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
+  put:
+    x-mojo-to: Auth::Providers#update
+    operationId: updateAuthProvider
+    tags:
+      - auth_providers
+    summary: Update an authentication provider
+    parameters:
+      - $ref: ../swagger.yaml#/parameters/auth_provider_id_pp
+      - name: body
+        in: body
+        description: |
+          A JSON object containing OAuth provider parameters.
+
+          The `config` object required attributes depends on the chosen `protocol`
+
+          ## OAuth
+
+          Requires:
+
+          * key
+          * secret
+          * authorize_url
+          * token_url
+
+          ## OIDC
+
+          Requires:
+
+          * key
+          * secret
+          * well_known_url
+        required: true
+        schema:
+          $ref: ../swagger.yaml#/definitions/auth_provider
+    produces:
+      - application/json
+    responses:
+      "200":
+        description: Updated authentication provider
+        schema:
+          $ref: ../swagger.yaml#/definitions/auth_provider
+      "400":
+        description: Bad Request
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "403":
+        description: Access forbidden
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "404":
+        description: Object not found
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
+  delete:
+    x-mojo-to: Auth::Providers#delete
+    operationId: delAuthProvider
+    tags:
+      - auth_providers
+    summary: Delete authentication provider
+    parameters:
+      - $ref: ../swagger.yaml#/parameters/auth_provider_id_pp
+    produces:
+      - application/json
+    responses:
+      "204":
+        description: Authentication provider deleted
+      "401":
+        description: Authentication required
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "403":
+        description: Access forbidden
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "404":
+        description: City not found
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
+"/auth/providers/{auth_provider_id}/domains":
+  get:
+    x-mojo-to: Auth::Provider::Domains#list
+    operationId: listAuthProviderDomains
+    tags:
+      - auth_providers
+    summary: Get authentication provider configured domains
+    parameters:
+      - $ref: ../swagger.yaml#/parameters/auth_provider_id_pp
+      - $ref: ../swagger.yaml#/parameters/match
+      - $ref: ../swagger.yaml#/parameters/order_by
+      - $ref: ../swagger.yaml#/parameters/page
+      - $ref: ../swagger.yaml#/parameters/per_page
+      - $ref: ../swagger.yaml#/parameters/q_param
+      - $ref: ../swagger.yaml#/parameters/q_body
+      - $ref: ../swagger.yaml#/parameters/q_header
+      - $ref: ../swagger.yaml#/parameters/request_id_header
+      - name: x-koha-embed
+        in: header
+        required: false
+        description: Embed list sent as a request header
+        type: array
+        items:
+          type: string
+          enum:
+            - domains
+        collectionFormat: csv
+    produces:
+      - application/json
+    responses:
+      "200":
+        description: An authentication provider
+        schema:
+          items:
+            $ref: ../swagger.yaml#/definitions/auth_provider_domain
+      "404":
+        description: Object not found
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
+  post:
+    x-mojo-to: Auth::Provider::Domains#add
+    operationId: addAuthProviderDomain
+    tags:
+      - auth_providers
+    summary: Add an authentication provider domain
+    parameters:
+      - $ref: ../swagger.yaml#/parameters/auth_provider_id_pp
+      - name: body
+        in: body
+        description: An authentication provider domain object
+        required: true
+        schema:
+          $ref: ../swagger.yaml#/definitions/auth_provider_domain
+    produces:
+      - application/json
+    responses:
+      "201":
+        description: Updated authentication provider domain
+        schema:
+          $ref: ../swagger.yaml#/definitions/auth_provider_domain
+      "400":
+        description: Bad Request
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "403":
+        description: Access forbidden
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "404":
+        description: Object not found
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
+"/auth/providers/{auth_provider_id}/domains/{auth_provider_domain_id}":
+  get:
+    x-mojo-to: Auth::Provider::Domains#get
+    operationId: getAuthProviderDomain
+    tags:
+      - auth_providers
+    summary: Get authentication provider domain
+    parameters:
+      - $ref: ../swagger.yaml#/parameters/auth_provider_id_pp
+      - $ref: ../swagger.yaml#/parameters/auth_provider_domain_id_pp
+    produces:
+      - application/json
+    responses:
+      "200":
+        description: An authentication provider
+        schema:
+          $ref: ../swagger.yaml#/definitions/auth_provider_domain
+      "404":
+        description: Object not found
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
+  delete:
+    x-mojo-to: Auth::Provider::Domains#delete
+    operationId: delAuthProviderDomain
+    tags:
+      - auth_providers
+    summary: Delete authentication provider
+    parameters:
+      - $ref: ../swagger.yaml#/parameters/auth_provider_id_pp
+      - $ref: ../swagger.yaml#/parameters/auth_provider_domain_id_pp
+    produces:
+      - application/json
+    responses:
+      "204":
+        description: Authentication provider deleted
+      "401":
+        description: Authentication required
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "403":
+        description: Access forbidden
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "404":
+        description: City not found
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+    x-koha-authorization:
+      permissions:
+        parameters: manage_authentication_providers
\ No newline at end of file
index 1d74ed6..e471336 100644 (file)
         description: Access forbidden
         schema:
           $ref: "../swagger.yaml#/definitions/error"
+"/oauth/login/{provider_code}/{interface}":
+  get:
+    x-mojo-to: OAuth::Client#login
+    operationId: loginOAuthClient
+    tags:
+      - oauth
+    summary: Login to OAuth provider
+    produces:
+      - application/json
+    parameters:
+      - name: provider_code
+        in: path
+        description: Code for 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
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
index dc88455..40fe951 100644 (file)
@@ -1,16 +1,16 @@
-"/public/oauth/login/{provider}/{interface}":
+"/public/oauth/login/{provider_code}/{interface}":
   get:
     x-mojo-to: OAuth::Client#login
-    operationId: loginOAuthClient
+    operationId: loginOAuthClientPublic
     tags:
       - oauth
     summary: Login to OAuth provider
     produces:
       - application/json
     parameters:
-      - name: provider
+      - name: provider_code
         in: path
-        description: Name of OAuth provider
+        description: Code for OAuth provider
         required: true
         type: string
       - name: interface
@@ -25,7 +25,8 @@
         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.
+        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
       "400":
         description: Bad Request
         schema:
-          $ref: "../swagger.yaml#/definitions/error"
+          $ref: ../swagger.yaml#/definitions/error
       "403":
         description: Access forbidden
         schema:
-          $ref: "../swagger.yaml#/definitions/error"
+          $ref: ../swagger.yaml#/definitions/error
+      "500":
+        description: |
+          Internal server error. Possible `error_code` attribute values:
+
+          * `internal_server_error`
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
+      "503":
+        description: Under maintenance
+        schema:
+          $ref: ../swagger.yaml#/definitions/error
index bd07d47..0344c0c 100644 (file)
@@ -8,6 +8,10 @@ definitions:
     $ref: ./definitions/advancededitormacro.yaml
   allows_renewal:
     $ref: ./definitions/allows_renewal.yaml
+  auth_provider:
+    "$ref": ./definitions/auth_provider.yaml
+  auth_provider_domain:
+    "$ref": ./definitions/auth_provider_domain.yaml
   basket:
     $ref: ./definitions/basket.yaml
   bundle_link:
@@ -125,6 +129,14 @@ paths:
     $ref: paths/auth.yaml#/~1auth~1two-factor~1registration
   /auth/two-factor/registration/verification:
     $ref: paths/auth.yaml#/~1auth~1two-factor~1registration~1verification
+  /auth/providers:
+    $ref: paths/auth.yaml#/~1auth~1providers
+  "/auth/providers/{auth_provider_id}":
+    $ref: paths/auth.yaml#/~1auth~1providers~1{auth_provider_id}
+  "/auth/providers/{auth_provider_id}/domains":
+    $ref: paths/auth.yaml#/~1auth~1providers~1{auth_provider_id}~1domains
+  "/auth/providers/{auth_provider_id}/domains/{auth_provider_domain_id}":
+    $ref: paths/auth.yaml#/~1auth~1providers~1{auth_provider_id}~1domains~1{auth_provider_domain_id}
   "/biblios/{biblio_id}":
     $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}"
   "/biblios/{biblio_id}/checkouts":
@@ -269,8 +281,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/oauth/login/{provider_code}/{interface}":
+    $ref: ./paths/public_oauth.yaml#/~1public~1oauth~1login~1{provider_code}~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":
@@ -324,6 +336,18 @@ parameters:
     name: agreement_period_id
     required: true
     type: integer
+  auth_provider_id_pp:
+    description: Authentication provider internal identifier
+    in: path
+    name: auth_provider_id
+    required: true
+    type: integer
+  auth_provider_domain_id_pp:
+    description: Authentication provider domain internal identifier
+    in: path
+    name: auth_provider_domain_id
+    required: true
+    type: integer
   biblio_id_pp:
     description: Record internal identifier
     in: path
@@ -656,6 +680,9 @@ tags:
   - description: "Manage article requests\n"
     name: article_requests
     x-displayName: Article requests
+  - description: "Manage authentication providers\n"
+    name: auth_providers
+    x-displayName: Authentication providers
   - description: "Manage baskets for the acquisitions module\n"
     name: baskets
     x-displayName: Baskets
index d55b7bb..197740b 100755 (executable)
@@ -19,7 +19,7 @@
 
 use Modern::Perl;
 
-use Test::More tests => 5;
+use Test::More tests => 6;
 
 use Test::MockModule;
 use Test::Exception;
@@ -84,6 +84,7 @@ subtest 'set_config() tests' => sub {
         plan tests => 4;
 
         my $provider = $builder->build_object( { class => 'Koha::Auth::Providers', value => { protocol => 'OIDC' } } );
+        $provider = $provider->upgrade_class;
 
         my $config = {
             key    => 'key',
@@ -93,12 +94,12 @@ subtest 'set_config() tests' => sub {
         throws_ok { $provider->set_config($config) }
         'Koha::Exceptions::MissingParameter', 'Exception thrown on missing parameter';
 
-        is( $@->error, 'The well_known_url parameter is mandatory', 'Message is correct' );
+        is( $@->parameter, 'well_known_url', 'Message is correct' );
 
         $config->{well_known_url} = 'https://koha-community.org/auth';
 
         my $return = $provider->set_config($config);
-        is( ref($return), 'Koha::Auth::Provider', 'Return type is correct' );
+        is( ref($return), 'Koha::Auth::Provider::OIDC', 'Return type is correct' );
 
         is_deeply( $provider->get_config, $config, 'Configuration stored correctly' );
     };
@@ -108,6 +109,7 @@ subtest 'set_config() tests' => sub {
         plan tests => 4;
 
         my $provider = $builder->build_object( { class => 'Koha::Auth::Providers', value => { protocol => 'OAuth' } } );
+        $provider = $provider->upgrade_class;
 
         my $config = {
             key       => 'key',
@@ -118,12 +120,12 @@ subtest 'set_config() tests' => sub {
         throws_ok { $provider->set_config($config) }
         'Koha::Exceptions::MissingParameter', 'Exception thrown on missing parameter';
 
-        is( $@->error, 'The authorize_url parameter is mandatory', 'Message is correct' );
+        is( $@->parameter, 'authorize_url', 'Message is correct' );
 
         $config->{authorize_url} = 'https://koha-community.org/auth/authorize';
 
         my $return = $provider->set_config($config);
-        is( ref($return), 'Koha::Auth::Provider', 'Return type is correct' );
+        is( ref($return), 'Koha::Auth::Provider::OAuth', 'Return type is correct' );
 
         is_deeply( $provider->get_config, $config, 'Configuration stored correctly' );
     };
@@ -137,7 +139,7 @@ subtest 'set_config() tests' => sub {
         throws_ok { $provider->set_config() }
         'Koha::Exception', 'Exception thrown on unsupported protocol';
 
-        like( "$@", qr/Unsupported protocol CAS/, 'Message is correct' );
+        like( "$@", qr/This method needs to be subclassed/, 'Message is correct' );
     };
 
     $schema->storage->txn_rollback;
@@ -177,3 +179,36 @@ subtest 'set_mapping() tests' => sub {
 
     $schema->storage->txn_rollback;
 };
+
+subtest 'upgrade_class() tests' => sub {
+
+    plan tests => 5;
+
+    $schema->storage->txn_begin;
+
+    my $mapping   = Koha::Auth::Provider::protocol_to_class_mapping;
+    my @protocols = keys %{ $mapping };
+
+    foreach my $protocol (@protocols) {
+
+        my $provider = $builder->build_object(
+            {
+                class => 'Koha::Auth::Providers',
+                value => { protocol => $protocol },
+            }
+        );
+
+        is( ref($provider), 'Koha::Auth::Provider', "Base class used for $protocol" );
+        # upgrade
+        $provider = $provider->upgrade_class;
+        is( ref($provider), $mapping->{$protocol}, "Class upgraded to " . $mapping->{$protocol} . "for protocol $protocol" );
+    }
+
+    my $provider = Koha::Auth::Provider->new({ protocol => 'Invalid' });
+    throws_ok
+      { $provider->upgrade }
+      'Koha::Exception',
+      'Exception throw on invalid protocol';
+
+    $schema->storage->txn_rollback;
+};