Bug 10190: fix permissions

- add a config API endpoint to Circulations Triggers so permissions can be fetched
- update the permissions in the Vue App so that manage_circ_rules is required
- update the permissions in the Circulation Rules API so that manage_circ_rules is required for PUT operations
- update the circulation-rules store to so that it is compatible with recently intorduced compostables
- implement a redirect into the route so that permission requirements are honoured

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Chloe Zermatten 2026-01-30 10:46:58 +00:00 committed by Pedro Amorim
parent 839bc81e80
commit cf8230cdda
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
10 changed files with 146 additions and 17 deletions

View File

@ -316,4 +316,25 @@ sub _all_kinds {
return \@all_valid_kinds;
}
=head3 config
Return the configuration options needed for the Circulations Triggers Vue app
=cut
sub config {
my $c = shift->openapi->valid_input or return;
my $patron = $c->stash('koha.user');
my $userflags = C4::Auth::haspermission( $patron->userid );
my $permissions = Koha::Auth::Permissions->get_authz_from_flags( { flags => $userflags } );
return $c->render(
status => 200,
openapi => {
permissions => $permissions,
},
);
}
1;

View File

@ -0,0 +1,7 @@
---
type: object
properties:
permissions:
type: object
description: List of permissions for the user
additionalProperties: false

View File

@ -0,0 +1,39 @@
---
/circulation_rules/config:
get:
x-mojo-to: CirculationRules#config
operationId: getCirculationRulesConfig
description: This resource returns a list of options needed for the Circulation Triggers Vue app
summary: get the CirculationRules config
tags:
- circulation_rules_config
produces:
- application/json
responses:
200:
description: The Circulation Rules config
schema:
$ref: "../swagger.yaml#/definitions/circulation_rules_config"
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:
- acquisition: '*'
- erm: 1

View File

@ -18,6 +18,8 @@ definitions:
$ref: ./definitions/authorised_value_category.yaml
circulation_rules:
$ref: ./definitions/circulation_rules.yaml
circulation_rules_config:
$ref: ./definitions/circulation_rules_config.yaml
identity_provider:
$ref: ./definitions/identity_provider.yaml
identity_provider_domain:
@ -355,6 +357,8 @@ paths:
$ref: ./paths/circulation_rules.yaml#/~1circulation_rules
/circulation_rules/kinds:
$ref: ./paths/circulation_rules.yaml#/~1circulation_rules~1kinds
/circulation_rules/config:
$ref: ./paths/circulation_rules_config.yaml#/~1circulation_rules~1config
/cities:
$ref: ./paths/cities.yaml#/~1cities
"/cities/{city_id}":
@ -1340,6 +1344,9 @@ tags:
- description: "Manage circulation rules\n"
name: circulation_rules
x-displayName: Circulation rules
- description: "Retrieve configuration relevant to circulation rules\n"
name: circulation_rules_config
x-displayName: Circulation rules config
- description: "Manage cities\n"
name: cities
x-displayName: Cities

View File

@ -96,7 +96,7 @@
<dt><a href="/cgi-bin/koha/admin/smart-rules.pl">Circulation and fine rules</a></dt>
<dd>Define circulation and fine rules for combinations of libraries, patron categories, and item types</dd>
[% END %]
[% IF ( CAN_user_tools_edit_notice_status_triggers ) %]
[% IF ( CAN_user_parameters_manage_circ_rules ) %]
<dt><a href="/cgi-bin/koha/admin/circulation_triggers">Circulation triggers</a></dt>
<dd>Define triggers for circulation notices</dd>
[% END %]

View File

@ -5,6 +5,15 @@ export class CircRuleAPIClient {
});
}
get config() {
return {
getAll: () =>
this.httpClient.get({
endpoint: "/config",
}),
};
}
get circ_rules() {
return {
getAll: (query, params, headers) =>

View File

@ -288,7 +288,6 @@ import { storeToRefs } from "pinia";
export default {
setup() {
const circRulesStore = inject("circRulesStore");
circRulesStore.init();
const {
updateTriggerCount,
setAllRawRuleSets,

View File

@ -29,6 +29,7 @@ import { inject } from "vue";
export default {
setup() {
const circRulesStore = inject("circRulesStore");
circRulesStore.init();
letters.unshift({
name: "No letter",
code: "",

View File

@ -51,7 +51,31 @@ app.provide("navigationStore", navigationStore);
app.provide("circRulesStore", circRulesStore);
app.mount("#__app");
router.beforeEach(to => {
navigationStore.$patch({ current: to.matched, params: to.params || {} });
removeMessages(); // This will actually flag the messages as displayed already
router.beforeEach((to, from, next) => {
circRulesStore
.loadUserPermissions()
.then(() => {
if (
!circRulesStore.isUserPermitted(
"CAN_user_parameters_manage_circ_rules"
)
) {
navigationStore.$patch({
navigationBlocked: true,
currentPermission: null,
});
removeMessages(); // This will actually flag the messages as displayed already
window.location.href = "/cgi-bin/koha/mainpage.pl";
return;
}
navigationStore.$patch({
current: to.matched,
params: to.params || {},
});
removeMessages(); // This will actually flag the messages as displayed already
next();
})
.catch(() => {
window.location.href = "/cgi-bin/koha/mainpage.pl";
});
});

View File

@ -2,16 +2,19 @@ import { defineStore } from "pinia";
import { $__ } from "../i18n";
import { APIClient } from "../fetch/api-client.js";
import { isEqual } from "lodash";
import { permissionsActions } from "../composables/permissions";
import { reactive, toRefs } from "vue";
export const useCircRulesStore = defineStore("circRules", {
// NOTES ON RULE SETS TYPES
// exhaustive: includes 'pure fallback' rules sets for contexts that no rules match.
// Format: [{overdue_X_<rule_name>: {value: mixed: isFallback: bool}}]
// effective: includes sets only for contexts for which one or more rule exists in the db. These sets will include fallbacks.
// Format: [{overdue_X_<rule_name>: <value>}}
// raw: includes only the exact sets as they are found in the db
// Format: [{overdue_X_<rule_name>: <value>}}]
state: () => ({
// NOTES ON RULE SETS TYPES
// exhaustive: includes 'pure fallback' rules sets for contexts that no rules match.
// Format: [{overdue_X_<rule_name>: {value: mixed: isFallback: bool}}]
// effective: includes sets only for contexts for which one or more rule exists in the db. These sets will include fallbacks.
// Format: [{overdue_X_<rule_name>: <value>}}
// raw: includes only the exact sets as they are found in the db
// Format: [{overdue_X_<rule_name>: <value>}}]
export const useCircRulesStore = defineStore("circRules", () => {
const store = reactive({
// context
currentLibraryId: "*",
currentPatronCategoryId: null,
@ -21,6 +24,7 @@ export const useCircRulesStore = defineStore("circRules", {
itemTypes: [],
libraries: [],
patronCategories: [],
userPermissions: null,
letters: [],
ruleSuffixes: ["delay", "notice", "mtt", "restrict", "has_rules"],
transportTypes: [
@ -36,14 +40,21 @@ export const useCircRulesStore = defineStore("circRules", {
allExhaustiveEffectiveRuleSets: [], // main data set for display all applied rules for current library
currentAndDefaultRawRuleSets: [], // data set to identify effective rules from (combines allDefaultLibraryRawRuleSets and allCurrentLibraryRawRuleSets)
storeInitialized: false,
}),
actions: {
});
const actions = {
// controllers
async init() {
await this.getItemTypes();
await this.getLibraries();
await this.getPatronCategories();
},
async loadUserPermissions() {
if (this.userPermissions !== null) {
return;
}
await this.getConfigurationOptions();
},
// utilities
formatTriggerSpecificRuleSetForDisplay(
context,
@ -403,6 +414,11 @@ export const useCircRulesStore = defineStore("circRules", {
throw e;
}
},
async getConfigurationOptions() {
const client = APIClient.circRule;
const { permissions } = await client.config.getAll();
this.userPermissions = permissions;
},
async getItemTypes() {
const client = APIClient.item;
let itemTypes = [];
@ -485,5 +501,11 @@ export const useCircRulesStore = defineStore("circRules", {
//TODO: handle e
}
},
},
...permissionsActions(store),
};
return {
...toRefs(store),
...actions,
};
});