Skip to content

Commit ed9fb78

Browse files
authored
Merge pull request #7680 from LibreSign/backport/7675/stable34
[stable34] fix: keep groups_request_sign JSON unicode serialization consistent
2 parents 35da462 + e8e1358 commit ed9fb78

11 files changed

Lines changed: 669 additions & 17 deletions

File tree

lib/Controller/AdminController.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,31 @@ private function saveOrDeleteConfig(string $key, ?string $value, string $default
960960
}
961961
}
962962

963+
/**
964+
* Persist groups allowed to request signatures as typed app config array
965+
*
966+
* @param list<string> $groups List of group IDs allowed to request signatures
967+
* @return DataResponse<Http::STATUS_OK, LibresignMessageResponse, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, LibresignErrorResponse, array{}>
968+
*
969+
* 200: Settings saved
970+
* 500: Internal server error
971+
*/
972+
#[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/admin/groups-request-sign/config', requirements: ['apiVersion' => '(v1)'])]
973+
public function setGroupsRequestSignConfig(array $groups = []): DataResponse {
974+
try {
975+
$normalizedGroups = array_values(array_map(static fn (mixed $group): string => (string)$group, $groups));
976+
$this->appConfig->setValueArray(Application::APP_ID, 'groups_request_sign', $normalizedGroups);
977+
978+
return new DataResponse([
979+
'message' => $this->l10n->t('Settings saved'),
980+
]);
981+
} catch (\Exception $e) {
982+
return new DataResponse([
983+
'error' => $e->getMessage(),
984+
], Http::STATUS_INTERNAL_SERVER_ERROR);
985+
}
986+
}
987+
963988
/**
964989
* Set signature flow configuration
965990
*
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Libresign\Migration;
11+
12+
use Closure;
13+
use OCA\Libresign\AppInfo\Application;
14+
use OCP\Exceptions\AppConfigTypeConflictException;
15+
use OCP\IAppConfig;
16+
use OCP\IDBConnection;
17+
use OCP\Migration\IOutput;
18+
use OCP\Migration\SimpleMigrationStep;
19+
20+
class Version18002Date20260511000000 extends SimpleMigrationStep {
21+
public function __construct(
22+
private readonly IAppConfig $appConfig,
23+
private readonly IDBConnection $connection,
24+
) {
25+
}
26+
27+
/**
28+
* @param IOutput $output
29+
* @param Closure $schemaClosure
30+
* @param array $options
31+
*/
32+
#[\Override]
33+
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
34+
$this->ensureArrayConfig('groups_request_sign', ['admin']);
35+
$this->ensureArrayConfig('approval_group', ['admin']);
36+
}
37+
38+
/**
39+
* @param list<string> $default
40+
*/
41+
private function ensureArrayConfig(string $key, array $default): void {
42+
$configValue = $this->getRawConfigValue($key);
43+
if ($configValue === null) {
44+
$this->appConfig->setValueArray(Application::APP_ID, $key, $default);
45+
return;
46+
}
47+
48+
$normalized = $this->normalizeConfigValue($configValue, $default);
49+
$this->forceArrayType($key);
50+
51+
try {
52+
$this->appConfig->setValueArray(Application::APP_ID, $key, $normalized);
53+
} catch (AppConfigTypeConflictException) {
54+
$this->forceArrayType($key);
55+
$this->appConfig->setValueArray(Application::APP_ID, $key, $normalized);
56+
}
57+
}
58+
59+
private function getRawConfigValue(string $key): ?string {
60+
$qb = $this->connection->getQueryBuilder();
61+
$qb->select('configvalue')
62+
->from('appconfig')
63+
->where($qb->expr()->eq('appid', $qb->createNamedParameter(Application::APP_ID)))
64+
->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)))
65+
->setMaxResults(1);
66+
67+
$result = $qb->executeQuery()->fetchOne();
68+
if ($result === false || $result === null) {
69+
return null;
70+
}
71+
72+
return (string)$result;
73+
}
74+
75+
private function forceArrayType(string $key): void {
76+
$qb = $this->connection->getQueryBuilder();
77+
$qb->update('appconfig')
78+
->set('type', $qb->createNamedParameter(IAppConfig::VALUE_ARRAY))
79+
->where($qb->expr()->eq('appid', $qb->createNamedParameter(Application::APP_ID)))
80+
->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)))
81+
->executeStatement();
82+
}
83+
84+
/**
85+
* @param list<string> $default
86+
* @return list<string>
87+
*/
88+
private function normalizeConfigValue(string $raw, array $default): array {
89+
$decoded = json_decode($raw, true);
90+
if (is_array($decoded)) {
91+
return array_values(array_map(static fn (mixed $item): string => (string)$item, $decoded));
92+
}
93+
94+
if ($raw === '') {
95+
return [];
96+
}
97+
98+
return $default;
99+
}
100+
}

lib/Service/Install/SignSetupService.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,15 @@ public function setResource(string $resource): self {
7070

7171
public function getArchitectures(): array {
7272
$appInfo = $this->appManager->getAppInfo(Application::APP_ID);
73-
if (empty($appInfo['dependencies']['architecture'])) {
73+
if (!is_array($appInfo) || !isset($appInfo['dependencies'])) {
7474
throw new \Exception('dependencies>architecture not found at info.xml');
7575
}
76-
return $appInfo['dependencies']['architecture'];
76+
/** @var list<string> $architectures */
77+
$architectures = $appInfo['dependencies']['architecture'] ?? [];
78+
if ($architectures === []) {
79+
throw new \Exception('dependencies>architecture not found at info.xml');
80+
}
81+
return $architectures;
7782
}
7883

7984
public function setPrivateKey(PrivateKey $privateKey): void {

openapi-administration.json

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3321,6 +3321,130 @@
33213321
}
33223322
}
33233323
},
3324+
"/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/groups-request-sign/config": {
3325+
"post": {
3326+
"operationId": "admin-set-groups-request-sign-config",
3327+
"summary": "Persist groups allowed to request signatures as typed app config array",
3328+
"description": "This endpoint requires admin access",
3329+
"tags": [
3330+
"admin"
3331+
],
3332+
"security": [
3333+
{
3334+
"bearer_auth": []
3335+
},
3336+
{
3337+
"basic_auth": []
3338+
}
3339+
],
3340+
"requestBody": {
3341+
"required": false,
3342+
"content": {
3343+
"application/json": {
3344+
"schema": {
3345+
"type": "object",
3346+
"properties": {
3347+
"groups": {
3348+
"type": "array",
3349+
"default": [],
3350+
"description": "List of group IDs allowed to request signatures",
3351+
"items": {
3352+
"type": "string"
3353+
}
3354+
}
3355+
}
3356+
}
3357+
}
3358+
}
3359+
},
3360+
"parameters": [
3361+
{
3362+
"name": "apiVersion",
3363+
"in": "path",
3364+
"required": true,
3365+
"schema": {
3366+
"type": "string",
3367+
"enum": [
3368+
"v1"
3369+
],
3370+
"default": "v1"
3371+
}
3372+
},
3373+
{
3374+
"name": "OCS-APIRequest",
3375+
"in": "header",
3376+
"description": "Required to be true for the API request to pass",
3377+
"required": true,
3378+
"schema": {
3379+
"type": "boolean",
3380+
"default": true
3381+
}
3382+
}
3383+
],
3384+
"responses": {
3385+
"200": {
3386+
"description": "Settings saved",
3387+
"content": {
3388+
"application/json": {
3389+
"schema": {
3390+
"type": "object",
3391+
"required": [
3392+
"ocs"
3393+
],
3394+
"properties": {
3395+
"ocs": {
3396+
"type": "object",
3397+
"required": [
3398+
"meta",
3399+
"data"
3400+
],
3401+
"properties": {
3402+
"meta": {
3403+
"$ref": "#/components/schemas/OCSMeta"
3404+
},
3405+
"data": {
3406+
"$ref": "#/components/schemas/MessageResponse"
3407+
}
3408+
}
3409+
}
3410+
}
3411+
}
3412+
}
3413+
}
3414+
},
3415+
"500": {
3416+
"description": "Internal server error",
3417+
"content": {
3418+
"application/json": {
3419+
"schema": {
3420+
"type": "object",
3421+
"required": [
3422+
"ocs"
3423+
],
3424+
"properties": {
3425+
"ocs": {
3426+
"type": "object",
3427+
"required": [
3428+
"meta",
3429+
"data"
3430+
],
3431+
"properties": {
3432+
"meta": {
3433+
"$ref": "#/components/schemas/OCSMeta"
3434+
},
3435+
"data": {
3436+
"$ref": "#/components/schemas/ErrorResponse"
3437+
}
3438+
}
3439+
}
3440+
}
3441+
}
3442+
}
3443+
}
3444+
}
3445+
}
3446+
}
3447+
},
33243448
"/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/signature-flow/config": {
33253449
"post": {
33263450
"operationId": "admin-set-signature-flow-config",

0 commit comments

Comments
 (0)