Skip to content

Commit 76ae59d

Browse files
committed
Merge branch 'feature/PB-39559_v4_12_13-Prepare-API-code' into 'release'
PB-39559 4.12.0-test.1 See merge request passbolt/passbolt-ce-api!341
2 parents 1595bfc + 7d09584 commit 76ae59d

File tree

81 files changed

+3114
-489
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+3114
-489
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [4.12.0-test.1] - 2025-03-05
6+
### Added
7+
- PB-39395 As an administrator I can contain permissions when upgrading folders to v5 format
8+
- PB-39394 As an administrator I can contain permissions when upgrading resources to v5 format
9+
- PB-38850 As an administrator I cannot rotate entities while two metadata keys are active
10+
- PB-37699 As an administrator I can upgrade folders to v5 format
11+
- PB-37363 As an administrator I can rotate metadata keys encrypting folders metadata
12+
- PB-36582 As an administrator I cannot reuse a previously deleted metadata key
13+
14+
### Fixed
15+
- PB-39512 Fix during metadata upgrade process, the resource_type_id field is now updated in the database
16+
- PB-39399 Adds missing fields to metadata private keys in index response
17+
- PB-39393 Fix limit value is null in pagination header response for rotate & upgrade endpoints
18+
- PB-38770 Fix email subject for delete resource email when resource is v
19+
- PB-38791 Fix 500 error on the duo MFA setup & verify page when duo service is unavailable
20+
- PB-38771 Fix unable to expire the metadata key due to expired datetime format
21+
522
## [4.11.1] - 2025-02-17
623
### Security
724
- PB-39045 Fix empty fullBaseUrl leading to Host header injection attack

RELEASE_NOTES.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
Release song: https://youtu.be/U16Xg_rQZkA?si=cVcmovGWluuo8oYj
1+
Release song: TBD
22

3-
Passbolt is pleased to announce the immediate availability of version v4.11.1. This version is a targeted security release of the API focusing on fixing the security issue reported by a security researcher.
3+
## [4.12.0-test.1] - 2025-03-05
4+
### Added
5+
- PB-39395 As an administrator I can contain permissions when upgrading folders to v5 format
6+
- PB-39394 As an administrator I can contain permissions when upgrading resources to v5 format
7+
- PB-38850 As an administrator I cannot rotate entities while two metadata keys are active
8+
- PB-37699 As an administrator I can upgrade folders to v5 format
9+
- PB-37363 As an administrator I can rotate metadata keys encrypting folders metadata
10+
- PB-36582 As an administrator I cannot reuse a previously deleted metadata key
411

5-
We would like to express our appreciation to the community for their assistance in making Passbolt more secure. Further details can be found in [the incident report](https://www.passbolt.com/incidents/host-header-injection-vulnerability).
6-
7-
## [4.11.1] - 2025-02-17
8-
### Security
9-
- PB-39045 Fix empty fullBaseUrl leading to Host header injection attack
12+
### Fixed
13+
- PB-39512 Fix during metadata upgrade process, the resource_type_id field is now updated in the database
14+
- PB-39399 Adds missing fields to metadata private keys in index response
15+
- PB-39393 Fix limit value is null in pagination header response for rotate & upgrade endpoints
16+
- PB-38770 Fix email subject for delete resource email when resource is v
17+
- PB-38791 Fix 500 error on the duo MFA setup & verify page when duo service is unavailable
18+
- PB-38771 Fix unable to expire the metadata key due to expired datetime format

config/version.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?php
22
return [
33
'passbolt' => [
4-
'version' => '4.11.1',
5-
'name' => 'Rebel Rebel',
4+
'version' => '4.12.0-test.1',
5+
'name' => 'TBD',
66
],
77
'php' => [
88
'minVersion' => '7.4',

package-lock.json

Lines changed: 7 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"jquery": "^3.5.1",
2525
"lockfile-lint": "^4.14.0",
2626
"openpgp": "^5.11.1",
27-
"passbolt-styleguide": "^4.11.0"
27+
"passbolt-styleguide": "^4.12.0"
2828
},
2929
"scripts": {
3030
"lint": "npm run lint:lockfile",

plugins/PassboltCe/Folders/src/Model/Table/FoldersTable.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Passbolt\Metadata\Model\Rule\IsFolderV5ToV4DowngradeAllowedRule;
3030
use Passbolt\Metadata\Model\Rule\IsMetadataKeyTypeAllowedBySettingsRule;
3131
use Passbolt\Metadata\Model\Rule\IsMetadataKeyTypeSharedOnSharedItemRule;
32+
use Passbolt\Metadata\Model\Rule\IsSharedMetadataKeyUniqueActiveRule;
3233
use Passbolt\Metadata\Model\Rule\IsV4ToV5UpgradeAllowedRule;
3334
use Passbolt\Metadata\Model\Rule\IsValidEncryptedMetadataRule;
3435
use Passbolt\Metadata\Model\Rule\MetadataKeyIdExistsInRule;
@@ -127,6 +128,9 @@ public function initialize(array $config): void
127128
'FoldersRelations.foreign_model' => 'Resource',
128129
],
129130
]);
131+
$this->belongsTo('MetadataKeys', [
132+
'className' => 'Passbolt/Metadata.MetadataKeys',
133+
]);
130134
}
131135

132136
/**
@@ -259,9 +263,14 @@ public function buildRulesV5(RulesChecker $rules): RulesChecker
259263
'message' => __('The metadata key is marked as expired.'),
260264
]);
261265

266+
$rules->add(new IsSharedMetadataKeyUniqueActiveRule(), 'isSharedMetadataKeyUniqueActive', [
267+
'errorField' => 'metadata_key_id',
268+
'message' => __('The shared metadata key should be unique.'),
269+
]);
270+
262271
$rules->add(new IsValidEncryptedMetadataRule(), 'isValidEncryptedMetadata', [
263272
'errorField' => 'metadata',
264-
'message' => __('The resource metadata provided can not be decrypted.'),
273+
'message' => __('The folder metadata provided cannot be decrypted.'),
265274
]);
266275

267276
$rules->addUpdate(

plugins/PassboltCe/Folders/src/Model/Traits/Folders/FoldersFindersTrait.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
use App\Model\Table\PermissionsTable;
2222
use App\Model\Traits\Query\CaseInsensitiveSearchQueryTrait;
2323
use Cake\Database\Expression\IdentifierExpression;
24+
use Cake\Database\Expression\QueryExpression;
2425
use Cake\ORM\Query;
2526
use Cake\Validation\Validation;
2627
use InvalidArgumentException;
2728
use Passbolt\Folders\Model\Behavior\FolderizableBehavior;
2829
use Passbolt\Folders\Model\Entity\Folder;
30+
use Passbolt\Metadata\Model\Entity\MetadataKey;
2931

3032
/**
3133
* Trait FoldersFindersTrait
@@ -154,6 +156,28 @@ public function findIndex(string $userId, ?array $options = [])
154156
return $query;
155157
}
156158

159+
/**
160+
* Returns all folders with expired metadata key.
161+
*
162+
* @return \Cake\ORM\Query
163+
*/
164+
public function findMetadataRotateKeyIndex(): Query
165+
{
166+
$query = $this->find();
167+
168+
return $query
169+
->where([
170+
'Folders.metadata_key_type' => MetadataKey::TYPE_SHARED_KEY,
171+
$query->newExpr()->isNotNull('Folders.metadata'),
172+
$query->newExpr()->isNotNull('Folders.metadata_key_id'),
173+
])
174+
->innerJoin(['MetadataKeys' => 'metadata_keys'], [
175+
'MetadataKeys.id' => new IdentifierExpression('Folders.metadata_key_id'),
176+
$query->newExpr()->isNotNull('MetadataKeys.expired'),
177+
])
178+
->disableHydration();
179+
}
180+
157181
/**
158182
* Build the query that fetches data for folders view
159183
*
@@ -259,4 +283,68 @@ public function filterQueryByParentIds(Query $query, array $parentIds): Query
259283
return $q->where(['OR' => $conditions]);
260284
});
261285
}
286+
287+
/**
288+
* Returns all resources in v4 format that need to be upgraded.
289+
*
290+
* @param array $options query options
291+
* @return \Cake\ORM\Query
292+
*/
293+
public function findMetadataUpgradeIndex(array $options): Query
294+
{
295+
$query = $this->find('v4')->disableHydration();
296+
297+
$containPermissions = (bool)($options['contain']['permissions'] ?? false);
298+
if ($containPermissions) {
299+
$query->contain('Permissions');
300+
}
301+
302+
if (!isset($options['filter']['is-shared'])) {
303+
return $query;
304+
}
305+
306+
$isShared = $options['filter']['is-shared'];
307+
$groupPermissionsCount = $this->Permissions->find()
308+
->select(['permissions_on_groups' => 'COUNT(*)'])
309+
->where([
310+
'Permissions.aco_foreign_key' => $query->identifier('Folders.id'),
311+
'Permissions.aco' => PermissionsTable::FOLDER_ACO,
312+
'Permissions.aro' => PermissionsTable::GROUP_ARO,
313+
]);
314+
$userPermissionsCount = $this->Permissions->find()
315+
->select(['permissions_on_users' => 'COUNT(*)'])
316+
->where([
317+
'Permissions.aco_foreign_key' => $query->identifier('Folders.id'),
318+
'Permissions.aco' => PermissionsTable::FOLDER_ACO,
319+
'Permissions.aro' => PermissionsTable::USER_ARO,
320+
]);
321+
if ($isShared === true) {
322+
// Is shared if at least one permission is a group permission
323+
// OR if at least two permissions are user permissions
324+
$query->where(function (QueryExpression $exp) use ($groupPermissionsCount, $userPermissionsCount) {
325+
return $exp->or(function (QueryExpression $or) use ($groupPermissionsCount, $userPermissionsCount) {
326+
return $or->gte($userPermissionsCount, 2)->gte($groupPermissionsCount, 1);
327+
});
328+
});
329+
} elseif ($isShared === false) {
330+
// Is not shared if no permission is a group permission
331+
// AND the only permission is a user permission
332+
$query->where(function (QueryExpression $exp) use ($groupPermissionsCount, $userPermissionsCount) {
333+
return $exp->eq($groupPermissionsCount, 0)->eq($userPermissionsCount, 1);
334+
});
335+
}
336+
337+
return $query;
338+
}
339+
340+
/**
341+
* @param \Cake\ORM\Query $query Query
342+
* @return \Cake\ORM\Query
343+
*/
344+
public function findV4(Query $query): Query
345+
{
346+
return $query->where([
347+
$query->newExpr()->isNull('Folders.metadata'),
348+
]);
349+
}
262350
}

plugins/PassboltCe/Folders/tests/Factory/FolderFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @method \Passbolt\Folders\Model\Entity\Folder getEntity()
3535
* @method \Passbolt\Folders\Model\Entity\Folder[] getEntities()
3636
* @method static \Passbolt\Folders\Model\Entity\Folder get($primaryKey, array $options = [])
37+
* @method static \Passbolt\Folders\Model\Entity\Folder firstOrFail($conditions = null)()
3738
*/
3839
class FolderFactory extends CakephpBaseFactory
3940
{

plugins/PassboltCe/Metadata/config/routes.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@
101101
['prefix' => 'Upgrade', 'controller' => 'MetadataUpgradeResourcesPost', 'action' => 'post']
102102
)
103103
->setMethods(['POST']);
104+
105+
$routes
106+
->connect(
107+
'/folders',
108+
['prefix' => 'Upgrade', 'controller' => 'MetadataUpgradeFoldersIndex', 'action' => 'index']
109+
)
110+
->setMethods(['GET']);
111+
112+
$routes
113+
->connect(
114+
'/folders',
115+
['prefix' => 'Upgrade', 'controller' => 'MetadataUpgradeFoldersPost', 'action' => 'post']
116+
)
117+
->setMethods(['POST']);
104118
});
105119

106120
/**
@@ -122,5 +136,19 @@
122136
['prefix' => 'RotateKey', 'controller' => 'MetadataRotateKeyResourcesPost', 'action' => 'post']
123137
)
124138
->setMethods(['POST']);
139+
140+
$routes
141+
->connect(
142+
'/folders',
143+
['prefix' => 'RotateKey', 'controller' => 'MetadataRotateKeyFoldersIndex', 'action' => 'index']
144+
)
145+
->setMethods(['GET']);
146+
147+
$routes
148+
->connect(
149+
'/folders',
150+
['prefix' => 'RotateKey', 'controller' => 'MetadataRotateKeyFoldersPost', 'action' => 'post']
151+
)
152+
->setMethods(['POST']);
125153
});
126154
});

plugins/PassboltCe/Metadata/src/Controller/Component/MetadataPaginationComponent.php

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ class MetadataPaginationComponent extends Component
3434
*
3535
* @var array<string, mixed>
3636
*/
37-
protected $_defaultConfig = [];
37+
protected $_defaultConfig = [
38+
'maxLimit' => self::MAX_PAGINATION_LIMIT,
39+
];
3840

3941
/**
4042
* Overwrites pagination limit with the one defined in configuration
@@ -46,20 +48,37 @@ class MetadataPaginationComponent extends Component
4648
*/
4749
public function initialize(array $config): void
4850
{
49-
$config = $this->setPaginationOptions($config);
5051
$this->getController()->loadComponent('ApiPagination', $config);
5152
$this->getController()->paginate['order'] = $config['order'] ?? [];
5253
$this->getController()->paginate['limit'] = $config['limit'] ?? [];
53-
$this->unsetDisallowedPaginationParams();
54+
$this->modifyPaginationOptionsInRequest();
5455
}
5556

5657
/**
57-
* Set pagination options.
58+
* Remove/modify pagination query parameters, those are controlled by configuration for security reasons.
5859
*
59-
* @param array $config component configuration
60-
* @return array
60+
* @return void
61+
*/
62+
private function modifyPaginationOptionsInRequest(): void
63+
{
64+
$params = $this->getController()->getRequest()->getQueryParams();
65+
66+
// page is not allowed to be controlled
67+
unset($params['page']);
68+
69+
// limit should be enforced via config
70+
$limit = $this->getConfigurationLimit();
71+
$params['limit'] = $limit;
72+
73+
$request = $this->getController()->getRequest()->withQueryParams($params);
74+
$this->getController()->setRequest($request);
75+
}
76+
77+
/**
78+
* @throws \Cake\Http\Exception\InternalErrorException When config value is invalid.
79+
* @return mixed
6180
*/
62-
private function setPaginationOptions(array $config): array
81+
public function getConfigurationLimit()
6382
{
6483
$limit = Configure::read('passbolt.plugins.metadata.defaultPaginationLimit');
6584

@@ -70,25 +89,6 @@ private function setPaginationOptions(array $config): array
7089
throw new InternalErrorException($message);
7190
}
7291

73-
$limit = max(min($limit, self::MAX_PAGINATION_LIMIT), self::MIN_PAGINATION_LIMIT);
74-
75-
return array_merge($config, [
76-
'limit' => $limit,
77-
'maxLimit' => self::MAX_PAGINATION_LIMIT,
78-
]);
79-
}
80-
81-
/**
82-
* Remove pagination query parameters, those are controlled by configuration for security reasons.
83-
*
84-
* @return void
85-
*/
86-
private function unsetDisallowedPaginationParams(): void
87-
{
88-
$params = $this->getController()->getRequest()->getQueryParams();
89-
unset($params['page']);
90-
unset($params['limit']);
91-
$request = $this->getController()->getRequest()->withQueryParams($params);
92-
$this->getController()->setRequest($request);
92+
return max(min($limit, self::MAX_PAGINATION_LIMIT), self::MIN_PAGINATION_LIMIT);
9393
}
9494
}

0 commit comments

Comments
 (0)