View | Details | Raw Unified | Return to bug 33503
Collapse All | Expand All

(-)a/Koha/REST/Plugin/PluginRoutes.pm (-3 / +74 lines)
Lines 102-112 sub inject_routes { Link Here
102
sub merge_spec {
102
sub merge_spec {
103
    my ( $spec, $plugin ) = @_;
103
    my ( $spec, $plugin ) = @_;
104
104
105
    if($plugin->can('api_routes')) {
105
    # Prefer the new api_spec() to inject a full OpenAPI2 spec instead of just the route definitions, if both are defined, to prevent duplicate routes.
106
    # This way we can use the same plugin release for the new and the old versions of Koha.
107
    if($plugin->can('api_routes') && not($plugin->can('api_spec'))) { # Deprecated
106
        my $plugin_spec = $plugin->api_routes;
108
        my $plugin_spec = $plugin->api_routes;
107
109
108
        foreach my $route ( keys %{ $plugin_spec } ) {
110
        foreach my $route ( keys %{ $plugin_spec } ) {
109
            my $THE_route = '/contrib/' . $plugin->api_namespace . $route;
111
            my $THE_route = get_normalized_plugin_path($plugin->api_namespace, $route);
110
            if ( exists $spec->{ $THE_route } ) {
112
            if ( exists $spec->{ $THE_route } ) {
111
                # Route exists, overwriting is forbidden
113
                # Route exists, overwriting is forbidden
112
                Koha::Exceptions::Plugin::ForbiddenAction->throw(
114
                Koha::Exceptions::Plugin::ForbiddenAction->throw(
Lines 118-129 sub merge_spec { Link Here
118
        }
120
        }
119
    }
121
    }
120
122
123
    if($plugin->can('api_spec')) {
124
        my $plugin_namespace = $plugin->api_namespace;
125
        my $plugin_spec = $plugin->api_spec;
126
        my @errors;
127
        if ($plugin_spec->{definitions}) {
128
            while (my ($k, $v) = each %{$plugin_spec->{definitions}}) {
129
                if ($spec->{definitions}->{$k}) {
130
                    push(@errors, "Duplicate OpenAPI definition '$k'. Plugin needs to rename one of it's definitions.");
131
                } else {
132
                    $spec->{definitions}->{$k} = $v;
133
                }
134
            }
135
        }
136
        if ($plugin_spec->{parameters}) {
137
            while (my ($k, $v) = each %{$plugin_spec->{parameters}}) {
138
                if ($spec->{parameters}->{$k}) {
139
                    push(@errors, "Duplicate OpenAPI parameter '$k'. Plugin needs to rename one of it's parameters.");
140
                } else {
141
                    $spec->{parameters}->{$k} = $v;
142
                }
143
            }
144
        }
145
        if ($plugin_spec->{tags}) {
146
            if ($spec->{tags}) {
147
                push(@{$spec->{tags}}, @{$plugin_spec->{tags}});
148
            } else {
149
                $spec->{tags} = $plugin_spec->{tags};
150
            }
151
        }
152
        if ($plugin_spec->{paths}) {
153
            while (my ($k, $v) = each %{$plugin_spec->{paths}}) {
154
                if ($spec->{paths}->{$k}) {
155
                    push(@errors, "Duplicate OpenAPI parameter '$k'. Plugin needs to rename one of it's paths.");
156
                } else {
157
                    $spec->{paths}->{get_normalized_plugin_path($plugin_namespace, $k)} = $v;
158
                }
159
            }
160
        }
161
        if (@errors) {
162
            Koha::Exceptions::Plugin::ForbiddenAction->throw(
163
                "Plugin '".$plugin->{class}."' had the following errors while merging OpenAPI specs:\n".join(', ', @errors)
164
            );
165
        }
166
    }
167
121
    if($plugin->can('static_routes')) {
168
    if($plugin->can('static_routes')) {
122
        my $plugin_spec = $plugin->static_routes;
169
        my $plugin_spec = $plugin->static_routes;
123
170
124
        foreach my $route ( keys %{ $plugin_spec } ) {
171
        foreach my $route ( keys %{ $plugin_spec } ) {
125
172
126
            my $THE_route = '/contrib/' . $plugin->api_namespace . '/static'.$route;
173
            my $THE_route = get_normalized_plugin_static_path($plugin->api_namespace, $route);
127
            if ( exists $spec->{ $THE_route } ) {
174
            if ( exists $spec->{ $THE_route } ) {
128
                # Route exists, overwriting is forbidden
175
                # Route exists, overwriting is forbidden
129
                Koha::Exceptions::Plugin::ForbiddenAction->throw(
176
                Koha::Exceptions::Plugin::ForbiddenAction->throw(
Lines 153-156 sub spec_ok { Link Here
153
    return !$schema->is_invalid;
200
    return !$schema->is_invalid;
154
}
201
}
155
202
203
=head3 get_normalized_plugin_path
204
205
public static subroutine
206
Normalizes a plugin's route path to /contrib/<plugin-namespace>/<plugin-route-path>
207
208
=cut
209
210
sub get_normalized_plugin_path {
211
    my ($plugin_api_namespace, $plugin_path_name) = @_;
212
    return '/contrib/' . $plugin_api_namespace . $plugin_path_name;
213
}
214
215
=head3 get_normalized_plugin_static_path
216
217
public static subroutine
218
Normalizes a plugin's static route path to /contrib/<plugin-namespace>/static/<plugin-route-path>
219
220
=cut
221
222
sub get_normalized_plugin_static_path {
223
    my ($plugin_api_namespace, $plugin_path_name) = @_;
224
    return '/contrib/' . $plugin_api_namespace . '/static' . $plugin_path_name;
225
}
226
156
1;
227
1;
(-)a/t/db_dependent/Koha/REST/Plugin/PluginRoutes.t (-1 / +2 lines)
Lines 129-135 subtest 'Disabled plugins tests' => sub { Link Here
129
129
130
subtest 'Permissions and access to plugin routes tests' => sub {
130
subtest 'Permissions and access to plugin routes tests' => sub {
131
131
132
    plan tests => 16;
132
    plan tests => 17;
133
133
134
    $schema->storage->txn_begin;
134
    $schema->storage->txn_begin;
135
135
Lines 164-169 subtest 'Permissions and access to plugin routes tests' => sub { Link Here
164
    my $routes = get_defined_routes($t);
164
    my $routes = get_defined_routes($t);
165
    ok( exists $routes->{'/contrib/testplugin/patrons/bother'}, 'Route exists' );
165
    ok( exists $routes->{'/contrib/testplugin/patrons/bother'}, 'Route exists' );
166
    ok( exists $routes->{'/contrib/testplugin/public/patrons/bother'}, 'Route exists' );
166
    ok( exists $routes->{'/contrib/testplugin/public/patrons/bother'}, 'Route exists' );
167
    ok( exists $routes->{'/contrib/bundledass/patrons/<:patron_id>/finger'}, 'Bundled Route exists' );
167
168
168
    C4::Context->set_preference( 'RESTPublicAnonymousRequests', 0 );
169
    C4::Context->set_preference( 'RESTPublicAnonymousRequests', 0 );
169
170
(-)a/t/lib/plugins/Koha/Plugin/BundledApiSpec.pm (+46 lines)
Line 0 Link Here
1
package Koha::Plugin::BundledApiSpec;
2
3
use Modern::Perl;
4
5
use base qw(Koha::Plugins::Base);
6
7
our $VERSION = 0.01;
8
our $metadata = {
9
    name            => 'Bundled OpenAPI Spec Plugin',
10
    author          => 'Olli-Antti Kivilahti',
11
    description     => 'Test plugin for testing the bundling of OpenAPI schema fragments',
12
    date_authored   => '2023-',
13
    date_updated    => '2023-04-06',
14
    minimum_version => '21.11',
15
    maximum_version => undef,
16
    version         => $VERSION,
17
    my_example_tag  => 'bundled_api_spec',
18
};
19
20
sub new {
21
    my ( $class, $args ) = @_;
22
    $args->{'metadata'} = $metadata;
23
    my $self = $class->SUPER::new($args);
24
    return $self;
25
}
26
27
sub api_namespace {
28
    return "bundledass";
29
}
30
31
# One should never define these both, but only the other.
32
#sub api_route {
33
#    my ( $self, $args ) = @_;
34
#    return JSON::Validator->new->schema($self->mbf_dir() . "/openapi.yaml")->schema->{data};
35
#}
36
37
# One should never define these both, but only the other. Automated test suite should disable the other for testing.
38
sub api_spec {
39
    my ( $self, $args ) = @_;
40
41
    my $schema2 = JSON::Validator::Schema::OpenAPIv2->new;
42
    $schema2->resolve($self->mbf_dir() . "/openapi.yaml");
43
    return $schema2->bundle->data;
44
}
45
46
1;
(-)a/t/lib/plugins/Koha/Plugin/BundledApiSpec/openapi.yaml (+69 lines)
Line 0 Link Here
1
---
2
3
swagger: "2.0"
4
info:
5
  title: API Title
6
  version: "1.0"
7
8
paths:
9
  /patrons/{patron_id}/finger:
10
    delete:
11
      x-mojo-to: Koha::Plugin::BundledApiSpec#finger_delete
12
      operationId: finger_delete
13
      tags:
14
        - plugin
15
      produces:
16
        - application/json
17
      parameters:
18
        - $ref: openapi/parameters.json#/patron_idPathParam
19
      x-koha-authorization:
20
        permissions:
21
          borrowers: finger
22
      responses:
23
        "200":
24
          description: Deleted ok
25
          schema:
26
            type: object
27
            required:
28
              - deleted_count
29
            properties:
30
              deleted_count:
31
                description: How many Block-objects have been deleted in the DB
32
                type: integer
33
    get:
34
      x-mojo-to: Koha::Plugin::BundledApiSpec#finger_get
35
      operationId: finger_get
36
      tags:
37
        - plugin
38
      produces:
39
        - application/json
40
      parameters:
41
        - $ref: openapi/parameters.json#/patron_idPathParam
42
      x-koha-authorization:
43
        permissions:
44
          borrowers: finger
45
      responses:
46
        "200":
47
          description: List of fingers
48
          schema:
49
            type: array
50
            items:
51
              $ref: openapi/definitions.json#/finger
52
    post:
53
      x-mojo-to: Koha::Plugin::BundledApiSpec#finger_post
54
      operationId: finger_post
55
      tags:
56
        - plugin
57
      produces:
58
        - application/json
59
      parameters:
60
        - $ref: openapi/parameters.json#/patron_idPathParam
61
        - $ref: openapi/parameters.json#/finger
62
      x-koha-authorization:
63
        permissions:
64
          borrowers: finger
65
      responses:
66
        "200":
67
          description: The created self-service block
68
          schema:
69
            $ref: openapi/definitions.json#/finger
(-)a/t/lib/plugins/Koha/Plugin/BundledApiSpec/openapi/definitions.json (+5 lines)
Line 0 Link Here
1
{
2
  "finger": {
3
    "$ref": "./definitions/finger.json"
4
  }
5
}
(-)a/t/lib/plugins/Koha/Plugin/BundledApiSpec/openapi/definitions/finger.json (+12 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "patron_id": {
5
      "type": "integer",
6
      "description": "The user that has the finger"
7
    },
8
    "type": {
9
      "type": "string"
10
    }
11
  }
12
}
(-)a/t/lib/plugins/Koha/Plugin/BundledApiSpec/openapi/parameters.json (-1 / +18 lines)
Line 0 Link Here
0
- 
1
{
2
  "patron_idPathParam": {
3
    "name": "patron_id",
4
    "in": "path",
5
    "description": "Internal Patron identifier",
6
    "required": true,
7
    "type": "integer"
8
  },
9
  "finger": {
10
    "name": "finger",
11
    "description": "Finger",
12
    "in": "body",
13
    "required": true,
14
    "schema": {
15
      "$ref": "./definitions.json#/finger"
16
    }
17
  }
18
}

Return to bug 33503