From f97ba16e44ddea2535792898f9e749b358fc30a4 Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Tue, 30 Aug 2022 10:33:14 -0300 Subject: [PATCH] Bug 31378: Add authentication provider endpoints 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 Signed-off-by: Lukasz Koszyk Signed-off-by: Nick Clemens Signed-off-by: Martin Renvoize Signed-off-by: Tomas Cohen Arazi --- Koha/Auth/Provider.pm | 85 +++- Koha/Auth/Provider/OAuth.pm | 65 +++ Koha/Auth/Provider/OIDC.pm | 64 +++ Koha/REST/V1/Auth/Provider/Domains.pm | 233 ++++++++++ Koha/REST/V1/Auth/Providers.pm | 237 ++++++++++ api/v1/swagger/definitions/auth_provider.yaml | 49 ++ .../definitions/auth_provider_domain.yaml | 48 ++ api/v1/swagger/paths/auth.yaml | 440 ++++++++++++++++++ api/v1/swagger/paths/oauth.yaml | 79 ++++ api/v1/swagger/paths/public_oauth.yaml | 26 +- api/v1/swagger/swagger.yaml | 31 +- t/db_dependent/Koha/Auth/Provider.t | 47 +- 12 files changed, 1376 insertions(+), 28 deletions(-) create mode 100644 Koha/Auth/Provider/OAuth.pm create mode 100644 Koha/Auth/Provider/OIDC.pm create mode 100644 Koha/REST/V1/Auth/Provider/Domains.pm create mode 100644 Koha/REST/V1/Auth/Providers.pm create mode 100644 api/v1/swagger/definitions/auth_provider.yaml create mode 100644 api/v1/swagger/definitions/auth_provider_domain.yaml diff --git a/Koha/Auth/Provider.pm b/Koha/Auth/Provider.pm index e8de84ada5..7a652dec55 100644 --- a/Koha/Auth/Provider.pm +++ b/Koha/Auth/Provider.pm @@ -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 codes and +implementing I. To be used by B. + +=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 index 0000000000..b77f4af5ad --- /dev/null +++ b/Koha/Auth/Provider/OAuth.pm @@ -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 . + +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 index 0000000000..5945555994 --- /dev/null +++ b/Koha/Auth/Provider/OIDC.pm @@ -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 . + +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 index 0000000000..f53954ac9f --- /dev/null +++ b/Koha/REST/V1/Auth/Provider/Domains.pm @@ -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 . + +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 index 0000000000..c31b2588bb --- /dev/null +++ b/Koha/REST/V1/Auth/Providers.pm @@ -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 . + +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 index 0000000000..93e587b2af --- /dev/null +++ b/api/v1/swagger/definitions/auth_provider.yaml @@ -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 index 0000000000..bc9f60a7a3 --- /dev/null +++ b/api/v1/swagger/definitions/auth_provider_domain.yaml @@ -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 diff --git a/api/v1/swagger/paths/auth.yaml b/api/v1/swagger/paths/auth.yaml index 40c886a5ba..ae9c655d21 100644 --- a/api/v1/swagger/paths/auth.yaml +++ b/api/v1/swagger/paths/auth.yaml @@ -128,3 +128,443 @@ 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 diff --git a/api/v1/swagger/paths/oauth.yaml b/api/v1/swagger/paths/oauth.yaml index 1d74ed6f1f..e471336ced 100644 --- a/api/v1/swagger/paths/oauth.yaml +++ b/api/v1/swagger/paths/oauth.yaml @@ -43,3 +43,82 @@ 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 diff --git a/api/v1/swagger/paths/public_oauth.yaml b/api/v1/swagger/paths/public_oauth.yaml index dc88455c1c..40fe951909 100644 --- a/api/v1/swagger/paths/public_oauth.yaml +++ b/api/v1/swagger/paths/public_oauth.yaml @@ -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 @@ -60,8 +61,19 @@ "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 diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index bd07d47908..0344c0c3b7 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -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 diff --git a/t/db_dependent/Koha/Auth/Provider.t b/t/db_dependent/Koha/Auth/Provider.t index d55b7bb000..197740ba5f 100755 --- a/t/db_dependent/Koha/Auth/Provider.t +++ b/t/db_dependent/Koha/Auth/Provider.t @@ -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; +}; -- 2.39.5