|
11 | 11 |
|
12 | 12 | use InvalidArgumentException; |
13 | 13 | use JsonException; |
| 14 | +use OC\AppFramework\Bootstrap\Coordinator; |
| 15 | +use OCP\ConfigLexicon\ConfigLexiconEntry; |
| 16 | +use OCP\ConfigLexicon\ConfigLexiconStrictness; |
| 17 | +use OCP\ConfigLexicon\IConfigLexiconEntry; |
| 18 | +use OCP\ConfigLexicon\ValueType; |
14 | 19 | use OCP\DB\Exception as DBException; |
15 | 20 | use OCP\DB\QueryBuilder\IQueryBuilder; |
16 | 21 | use OCP\Exceptions\AppConfigIncorrectTypeException; |
@@ -55,6 +60,8 @@ class AppConfig implements IAppConfig { |
55 | 60 | private array $valueTypes = []; // type for all config values |
56 | 61 | private bool $fastLoaded = false; |
57 | 62 | private bool $lazyLoaded = false; |
| 63 | + /** @var array<array-key, array{entries: array<array-key, IConfigLexiconEntry>, strict: bool}> ['app_id' => ['strict' => bool, 'entries' => ['config_key' => IConfigLexiconEntry[]]] */ |
| 64 | + private array $configLexiconDetails = []; |
58 | 65 |
|
59 | 66 | /** |
60 | 67 | * $migrationCompleted is only needed to manage the previous structure |
@@ -430,6 +437,10 @@ private function getTypedValue( |
430 | 437 | int $type, |
431 | 438 | ): string { |
432 | 439 | $this->assertParams($app, $key, valueType: $type); |
| 440 | + if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type, $default)) { |
| 441 | + return $default; // returns default if strictness of lexicon is set to WARNING (block and report) |
| 442 | + } |
| 443 | + |
433 | 444 | $this->loadConfig($lazy); |
434 | 445 |
|
435 | 446 | /** |
@@ -721,6 +732,9 @@ private function setTypedValue( |
721 | 732 | int $type, |
722 | 733 | ): bool { |
723 | 734 | $this->assertParams($app, $key); |
| 735 | + if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type)) { |
| 736 | + return false; // returns false as database is not updated |
| 737 | + } |
724 | 738 | $this->loadConfig($lazy); |
725 | 739 |
|
726 | 740 | $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type); |
@@ -1538,4 +1552,109 @@ private function getSensitiveKeys(string $app): array { |
1538 | 1552 | public function clearCachedConfig(): void { |
1539 | 1553 | $this->clearCache(); |
1540 | 1554 | } |
| 1555 | + |
| 1556 | + /** |
| 1557 | + * verify and compare current use of config values with defined lexicon |
| 1558 | + * |
| 1559 | + * @throws AppConfigUnknownKeyException |
| 1560 | + * @throws AppConfigTypeConflictException |
| 1561 | + */ |
| 1562 | + private function compareRegisteredConfigValues( |
| 1563 | + string $app, |
| 1564 | + string $key, |
| 1565 | + bool &$lazy, |
| 1566 | + int &$type, |
| 1567 | + string &$default = '', |
| 1568 | + ): bool { |
| 1569 | + if (in_array($key, |
| 1570 | + [ |
| 1571 | + 'enabled', |
| 1572 | + 'installed_version', |
| 1573 | + 'types', |
| 1574 | + ])) { |
| 1575 | + return false; |
| 1576 | + } |
| 1577 | + $configDetails = $this->getConfigDetailsFromLexicon($app); |
| 1578 | + if (!array_key_exists($key, $configDetails['entries'])) { |
| 1579 | + return $this->applyLexiconStrictness($app, $key, $configDetails['strictness']); |
| 1580 | + } |
| 1581 | + |
| 1582 | + /** @var ConfigLexiconEntry $configValue */ |
| 1583 | + $configValue = $configDetails['entries'][$key]; |
| 1584 | + $type &= ~self::VALUE_SENSITIVE; |
| 1585 | + |
| 1586 | + if ($type === self::VALUE_MIXED) { |
| 1587 | + $type = $configValue->getValueType()->value; // we overwrite if value was requested as mixed |
| 1588 | + } else if ($configValue->getValueType()->value !== $type) { |
| 1589 | + throw new AppConfigTypeConflictException('The key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon'); |
| 1590 | + } |
| 1591 | + |
| 1592 | + $lazy = $configValue->isLazy(); |
| 1593 | + $default = $configValue->getDefault() ?? $default; // default from Lexicon got priority |
| 1594 | + if ($configValue->isSensitive()) { |
| 1595 | + $type |= self::VALUE_SENSITIVE; |
| 1596 | + } |
| 1597 | + if ($configValue->isDeprecated()) { |
| 1598 | + $this->logger->notice('config value ' . $app . '/' . $key . ' is set as deprecated.'); |
| 1599 | + } |
| 1600 | + |
| 1601 | + return true; |
| 1602 | + } |
| 1603 | + |
| 1604 | + /** |
| 1605 | + * @param string $app |
| 1606 | + * @param string $key |
| 1607 | + * @param ConfigLexiconStrictness $strictness |
| 1608 | + * |
| 1609 | + * @return bool TRUE if conflict can be fully ignored |
| 1610 | + * @throws AppConfigUnknownKeyException |
| 1611 | + */ |
| 1612 | + private function applyLexiconStrictness( |
| 1613 | + string $app, |
| 1614 | + string $key, |
| 1615 | + ?ConfigLexiconStrictness $strictness |
| 1616 | + ): bool { |
| 1617 | + if ($strictness === null) { |
| 1618 | + return true; |
| 1619 | + } |
| 1620 | + |
| 1621 | + $line = 'The key ' . $app . '/' . $key . ' is not defined in the config lexicon'; |
| 1622 | + switch($strictness) { |
| 1623 | + case ConfigLexiconStrictness::IGNORE: |
| 1624 | + return true; |
| 1625 | + case ConfigLexiconStrictness::NOTICE: |
| 1626 | + $this->logger->notice($line); |
| 1627 | + return true; |
| 1628 | + case ConfigLexiconStrictness::WARNING: |
| 1629 | + $this->logger->warning($line); |
| 1630 | + return false; |
| 1631 | + } |
| 1632 | + |
| 1633 | + throw new AppConfigUnknownKeyException($line); |
| 1634 | + } |
| 1635 | + |
| 1636 | + /** |
| 1637 | + * extract details from registered $appId's config lexicon |
| 1638 | + * |
| 1639 | + * @param string $appId |
| 1640 | + * |
| 1641 | + * @return array{entries: array<array-key, IConfigLexiconEntry>, strict: bool} |
| 1642 | + */ |
| 1643 | + private function getConfigDetailsFromLexicon(string $appId): array { |
| 1644 | + if (!array_key_exists($appId, $this->configLexiconDetails)) { |
| 1645 | + $entries = []; |
| 1646 | + $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); |
| 1647 | + $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId); |
| 1648 | + foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) { |
| 1649 | + $entries[$configEntry->getKey()] = $configEntry; |
| 1650 | + } |
| 1651 | + |
| 1652 | + $this->configLexiconDetails[$appId] = [ |
| 1653 | + 'entries' => $entries, |
| 1654 | + 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE |
| 1655 | + ]; |
| 1656 | + } |
| 1657 | + |
| 1658 | + return $this->configLexiconDetails[$appId]; |
| 1659 | + } |
1541 | 1660 | } |
0 commit comments