@@ -21,82 +21,82 @@ discard block |
||
| 21 | 21 | $nextcloudDir = dirname(__DIR__); |
| 22 | 22 | |
| 23 | 23 | class NextcloudNamespaceSkipVoter implements ClassNameImportSkipVoterInterface { |
| 24 | - private array $namespacePrefixes = [ |
|
| 25 | - 'OC', |
|
| 26 | - 'OCA', |
|
| 27 | - 'OCP', |
|
| 28 | - ]; |
|
| 29 | - private array $skippedClassNames = [ |
|
| 30 | - 'Backend', |
|
| 31 | - 'Connection', |
|
| 32 | - 'Exception', |
|
| 33 | - 'IManager', |
|
| 34 | - 'IProvider', |
|
| 35 | - 'Manager', |
|
| 36 | - 'Plugin', |
|
| 37 | - 'Provider', |
|
| 38 | - ]; |
|
| 39 | - public function shouldSkip(File $file, FullyQualifiedObjectType $fullyQualifiedObjectType, Node $node) : bool { |
|
| 40 | - if (in_array($fullyQualifiedObjectType->getShortName(), $this->skippedClassNames)) { |
|
| 41 | - // Skip common class names to avoid confusion |
|
| 42 | - return true; |
|
| 43 | - } |
|
| 44 | - foreach ($this->namespacePrefixes as $prefix) { |
|
| 45 | - if (str_starts_with($fullyQualifiedObjectType->getClassName(), $prefix . '\\')) { |
|
| 46 | - // Import Nextcloud namespaces |
|
| 47 | - return false; |
|
| 48 | - } |
|
| 49 | - } |
|
| 50 | - // Skip everything else |
|
| 51 | - return true; |
|
| 52 | - } |
|
| 24 | + private array $namespacePrefixes = [ |
|
| 25 | + 'OC', |
|
| 26 | + 'OCA', |
|
| 27 | + 'OCP', |
|
| 28 | + ]; |
|
| 29 | + private array $skippedClassNames = [ |
|
| 30 | + 'Backend', |
|
| 31 | + 'Connection', |
|
| 32 | + 'Exception', |
|
| 33 | + 'IManager', |
|
| 34 | + 'IProvider', |
|
| 35 | + 'Manager', |
|
| 36 | + 'Plugin', |
|
| 37 | + 'Provider', |
|
| 38 | + ]; |
|
| 39 | + public function shouldSkip(File $file, FullyQualifiedObjectType $fullyQualifiedObjectType, Node $node) : bool { |
|
| 40 | + if (in_array($fullyQualifiedObjectType->getShortName(), $this->skippedClassNames)) { |
|
| 41 | + // Skip common class names to avoid confusion |
|
| 42 | + return true; |
|
| 43 | + } |
|
| 44 | + foreach ($this->namespacePrefixes as $prefix) { |
|
| 45 | + if (str_starts_with($fullyQualifiedObjectType->getClassName(), $prefix . '\\')) { |
|
| 46 | + // Import Nextcloud namespaces |
|
| 47 | + return false; |
|
| 48 | + } |
|
| 49 | + } |
|
| 50 | + // Skip everything else |
|
| 51 | + return true; |
|
| 52 | + } |
|
| 53 | 53 | } |
| 54 | 54 | |
| 55 | 55 | $config = RectorConfig::configure() |
| 56 | - ->withPaths([ |
|
| 57 | - $nextcloudDir . '/apps', |
|
| 58 | - $nextcloudDir . '/core', |
|
| 59 | - $nextcloudDir . '/ocs', |
|
| 60 | - $nextcloudDir . '/ocs-provider', |
|
| 61 | - $nextcloudDir . '/console.php', |
|
| 62 | - $nextcloudDir . '/cron.php', |
|
| 63 | - $nextcloudDir . '/index.php', |
|
| 64 | - $nextcloudDir . '/occ', |
|
| 65 | - $nextcloudDir . '/public.php', |
|
| 66 | - $nextcloudDir . '/remote.php', |
|
| 67 | - $nextcloudDir . '/status.php', |
|
| 68 | - $nextcloudDir . '/version.php', |
|
| 69 | - $nextcloudDir . '/lib/private/Share20/ProviderFactory.php', |
|
| 70 | - $nextcloudDir . '/tests', |
|
| 71 | - // $nextcloudDir . '/config', |
|
| 72 | - // $nextcloudDir . '/lib', |
|
| 73 | - // $nextcloudDir . '/themes', |
|
| 74 | - ]) |
|
| 75 | - ->withSkip([ |
|
| 76 | - $nextcloudDir . '/apps/*/3rdparty/*', |
|
| 77 | - $nextcloudDir . '/apps/*/build/stubs/*', |
|
| 78 | - $nextcloudDir . '/apps/*/composer/*', |
|
| 79 | - $nextcloudDir . '/apps/*/config/*', |
|
| 80 | - // The mock classes are excluded, as the tests explicitly test the annotations which should not be migrated to attributes |
|
| 81 | - $nextcloudDir . '/tests/lib/AppFramework/Middleware/Mock/*', |
|
| 82 | - $nextcloudDir . '/tests/lib/AppFramework/Middleware/Security/Mock/*', |
|
| 83 | - ]) |
|
| 84 | - // uncomment to reach your current PHP version |
|
| 85 | - // ->withPhpSets() |
|
| 86 | - ->withImportNames(importShortClasses:false) |
|
| 87 | - ->withTypeCoverageLevel(0) |
|
| 88 | - ->withRules([ |
|
| 89 | - UseSpecificWillMethodRector::class, |
|
| 90 | - StaticDataProviderClassMethodRector::class, |
|
| 91 | - DataProviderAnnotationToAttributeRector::class, |
|
| 92 | - ]) |
|
| 93 | - ->withConfiguredRule(ClassPropertyAssignToConstructorPromotionRector::class, [ |
|
| 94 | - 'inline_public' => true, |
|
| 95 | - 'rename_property' => true, |
|
| 96 | - ]) |
|
| 97 | - ->withSets([ |
|
| 98 | - NextcloudSets::NEXTCLOUD_27, |
|
| 99 | - ]); |
|
| 56 | + ->withPaths([ |
|
| 57 | + $nextcloudDir . '/apps', |
|
| 58 | + $nextcloudDir . '/core', |
|
| 59 | + $nextcloudDir . '/ocs', |
|
| 60 | + $nextcloudDir . '/ocs-provider', |
|
| 61 | + $nextcloudDir . '/console.php', |
|
| 62 | + $nextcloudDir . '/cron.php', |
|
| 63 | + $nextcloudDir . '/index.php', |
|
| 64 | + $nextcloudDir . '/occ', |
|
| 65 | + $nextcloudDir . '/public.php', |
|
| 66 | + $nextcloudDir . '/remote.php', |
|
| 67 | + $nextcloudDir . '/status.php', |
|
| 68 | + $nextcloudDir . '/version.php', |
|
| 69 | + $nextcloudDir . '/lib/private/Share20/ProviderFactory.php', |
|
| 70 | + $nextcloudDir . '/tests', |
|
| 71 | + // $nextcloudDir . '/config', |
|
| 72 | + // $nextcloudDir . '/lib', |
|
| 73 | + // $nextcloudDir . '/themes', |
|
| 74 | + ]) |
|
| 75 | + ->withSkip([ |
|
| 76 | + $nextcloudDir . '/apps/*/3rdparty/*', |
|
| 77 | + $nextcloudDir . '/apps/*/build/stubs/*', |
|
| 78 | + $nextcloudDir . '/apps/*/composer/*', |
|
| 79 | + $nextcloudDir . '/apps/*/config/*', |
|
| 80 | + // The mock classes are excluded, as the tests explicitly test the annotations which should not be migrated to attributes |
|
| 81 | + $nextcloudDir . '/tests/lib/AppFramework/Middleware/Mock/*', |
|
| 82 | + $nextcloudDir . '/tests/lib/AppFramework/Middleware/Security/Mock/*', |
|
| 83 | + ]) |
|
| 84 | + // uncomment to reach your current PHP version |
|
| 85 | + // ->withPhpSets() |
|
| 86 | + ->withImportNames(importShortClasses:false) |
|
| 87 | + ->withTypeCoverageLevel(0) |
|
| 88 | + ->withRules([ |
|
| 89 | + UseSpecificWillMethodRector::class, |
|
| 90 | + StaticDataProviderClassMethodRector::class, |
|
| 91 | + DataProviderAnnotationToAttributeRector::class, |
|
| 92 | + ]) |
|
| 93 | + ->withConfiguredRule(ClassPropertyAssignToConstructorPromotionRector::class, [ |
|
| 94 | + 'inline_public' => true, |
|
| 95 | + 'rename_property' => true, |
|
| 96 | + ]) |
|
| 97 | + ->withSets([ |
|
| 98 | + NextcloudSets::NEXTCLOUD_27, |
|
| 99 | + ]); |
|
| 100 | 100 | |
| 101 | 101 | $config->registerService(NextcloudNamespaceSkipVoter::class, tag:ClassNameImportSkipVoterInterface::class); |
| 102 | 102 | |
@@ -108,11 +108,11 @@ discard block |
||
| 108 | 108 | $ignoredEntries = array_values($ignoredEntries); |
| 109 | 109 | |
| 110 | 110 | foreach ($ignoredEntries as $ignoredEntry) { |
| 111 | - if (str_ends_with($ignoredEntry, '/')) { |
|
| 112 | - $config->withSkip([$ignoredEntry . '*']); |
|
| 113 | - } else { |
|
| 114 | - $config->withSkip([$ignoredEntry . '/*']); |
|
| 115 | - } |
|
| 111 | + if (str_ends_with($ignoredEntry, '/')) { |
|
| 112 | + $config->withSkip([$ignoredEntry . '*']); |
|
| 113 | + } else { |
|
| 114 | + $config->withSkip([$ignoredEntry . '/*']); |
|
| 115 | + } |
|
| 116 | 116 | } |
| 117 | 117 | |
| 118 | 118 | return $config; |
@@ -42,7 +42,7 @@ discard block |
||
| 42 | 42 | return true; |
| 43 | 43 | } |
| 44 | 44 | foreach ($this->namespacePrefixes as $prefix) { |
| 45 | - if (str_starts_with($fullyQualifiedObjectType->getClassName(), $prefix . '\\')) { |
|
| 45 | + if (str_starts_with($fullyQualifiedObjectType->getClassName(), $prefix.'\\')) { |
|
| 46 | 46 | // Import Nextcloud namespaces |
| 47 | 47 | return false; |
| 48 | 48 | } |
@@ -54,32 +54,32 @@ discard block |
||
| 54 | 54 | |
| 55 | 55 | $config = RectorConfig::configure() |
| 56 | 56 | ->withPaths([ |
| 57 | - $nextcloudDir . '/apps', |
|
| 58 | - $nextcloudDir . '/core', |
|
| 59 | - $nextcloudDir . '/ocs', |
|
| 60 | - $nextcloudDir . '/ocs-provider', |
|
| 61 | - $nextcloudDir . '/console.php', |
|
| 62 | - $nextcloudDir . '/cron.php', |
|
| 63 | - $nextcloudDir . '/index.php', |
|
| 64 | - $nextcloudDir . '/occ', |
|
| 65 | - $nextcloudDir . '/public.php', |
|
| 66 | - $nextcloudDir . '/remote.php', |
|
| 67 | - $nextcloudDir . '/status.php', |
|
| 68 | - $nextcloudDir . '/version.php', |
|
| 69 | - $nextcloudDir . '/lib/private/Share20/ProviderFactory.php', |
|
| 70 | - $nextcloudDir . '/tests', |
|
| 57 | + $nextcloudDir.'/apps', |
|
| 58 | + $nextcloudDir.'/core', |
|
| 59 | + $nextcloudDir.'/ocs', |
|
| 60 | + $nextcloudDir.'/ocs-provider', |
|
| 61 | + $nextcloudDir.'/console.php', |
|
| 62 | + $nextcloudDir.'/cron.php', |
|
| 63 | + $nextcloudDir.'/index.php', |
|
| 64 | + $nextcloudDir.'/occ', |
|
| 65 | + $nextcloudDir.'/public.php', |
|
| 66 | + $nextcloudDir.'/remote.php', |
|
| 67 | + $nextcloudDir.'/status.php', |
|
| 68 | + $nextcloudDir.'/version.php', |
|
| 69 | + $nextcloudDir.'/lib/private/Share20/ProviderFactory.php', |
|
| 70 | + $nextcloudDir.'/tests', |
|
| 71 | 71 | // $nextcloudDir . '/config', |
| 72 | 72 | // $nextcloudDir . '/lib', |
| 73 | 73 | // $nextcloudDir . '/themes', |
| 74 | 74 | ]) |
| 75 | 75 | ->withSkip([ |
| 76 | - $nextcloudDir . '/apps/*/3rdparty/*', |
|
| 77 | - $nextcloudDir . '/apps/*/build/stubs/*', |
|
| 78 | - $nextcloudDir . '/apps/*/composer/*', |
|
| 79 | - $nextcloudDir . '/apps/*/config/*', |
|
| 76 | + $nextcloudDir.'/apps/*/3rdparty/*', |
|
| 77 | + $nextcloudDir.'/apps/*/build/stubs/*', |
|
| 78 | + $nextcloudDir.'/apps/*/composer/*', |
|
| 79 | + $nextcloudDir.'/apps/*/config/*', |
|
| 80 | 80 | // The mock classes are excluded, as the tests explicitly test the annotations which should not be migrated to attributes |
| 81 | - $nextcloudDir . '/tests/lib/AppFramework/Middleware/Mock/*', |
|
| 82 | - $nextcloudDir . '/tests/lib/AppFramework/Middleware/Security/Mock/*', |
|
| 81 | + $nextcloudDir.'/tests/lib/AppFramework/Middleware/Mock/*', |
|
| 82 | + $nextcloudDir.'/tests/lib/AppFramework/Middleware/Security/Mock/*', |
|
| 83 | 83 | ]) |
| 84 | 84 | // uncomment to reach your current PHP version |
| 85 | 85 | // ->withPhpSets() |
@@ -101,7 +101,7 @@ discard block |
||
| 101 | 101 | $config->registerService(NextcloudNamespaceSkipVoter::class, tag:ClassNameImportSkipVoterInterface::class); |
| 102 | 102 | |
| 103 | 103 | /* Ignore all files ignored by git */ |
| 104 | -$ignoredEntries = shell_exec('git status --porcelain --ignored ' . escapeshellarg($nextcloudDir)); |
|
| 104 | +$ignoredEntries = shell_exec('git status --porcelain --ignored '.escapeshellarg($nextcloudDir)); |
|
| 105 | 105 | $ignoredEntries = explode("\n", $ignoredEntries); |
| 106 | 106 | $ignoredEntries = array_filter($ignoredEntries, static fn (string $line) => str_starts_with($line, '!! ')); |
| 107 | 107 | $ignoredEntries = array_map(static fn (string $line) => substr($line, 3), $ignoredEntries); |
@@ -109,9 +109,9 @@ discard block |
||
| 109 | 109 | |
| 110 | 110 | foreach ($ignoredEntries as $ignoredEntry) { |
| 111 | 111 | if (str_ends_with($ignoredEntry, '/')) { |
| 112 | - $config->withSkip([$ignoredEntry . '*']); |
|
| 112 | + $config->withSkip([$ignoredEntry.'*']); |
|
| 113 | 113 | } else { |
| 114 | - $config->withSkip([$ignoredEntry . '/*']); |
|
| 114 | + $config->withSkip([$ignoredEntry.'/*']); |
|
| 115 | 115 | } |
| 116 | 116 | } |
| 117 | 117 | |
@@ -19,225 +19,225 @@ |
||
| 19 | 19 | use Symfony\Component\Console\Output\OutputInterface; |
| 20 | 20 | |
| 21 | 21 | class Base extends Command implements CompletionAwareInterface { |
| 22 | - public const OUTPUT_FORMAT_PLAIN = 'plain'; |
|
| 23 | - public const OUTPUT_FORMAT_JSON = 'json'; |
|
| 24 | - public const OUTPUT_FORMAT_JSON_PRETTY = 'json_pretty'; |
|
| 25 | - |
|
| 26 | - protected string $defaultOutputFormat = self::OUTPUT_FORMAT_PLAIN; |
|
| 27 | - private bool $php_pcntl_signal = false; |
|
| 28 | - private bool $interrupted = false; |
|
| 29 | - |
|
| 30 | - protected function configure() { |
|
| 31 | - // Some of our commands do not extend this class; and some of those that do do not call parent::configure() |
|
| 32 | - $defaultHelp = 'More extensive and thorough documentation may be found at ' . Server::get(Defaults::class)->getDocBaseUrl() . PHP_EOL; |
|
| 33 | - $this |
|
| 34 | - ->setHelp($defaultHelp) |
|
| 35 | - ->addOption( |
|
| 36 | - 'output', |
|
| 37 | - null, |
|
| 38 | - InputOption::VALUE_OPTIONAL, |
|
| 39 | - 'Output format (plain, json or json_pretty, default is plain)', |
|
| 40 | - $this->defaultOutputFormat |
|
| 41 | - ) |
|
| 42 | - ; |
|
| 43 | - } |
|
| 44 | - |
|
| 45 | - protected function writeArrayInOutputFormat(InputInterface $input, OutputInterface $output, iterable $items, string $prefix = ' - '): void { |
|
| 46 | - switch ($input->getOption('output')) { |
|
| 47 | - case self::OUTPUT_FORMAT_JSON: |
|
| 48 | - $items = (is_array($items) ? $items : iterator_to_array($items)); |
|
| 49 | - $output->writeln(json_encode($items)); |
|
| 50 | - break; |
|
| 51 | - case self::OUTPUT_FORMAT_JSON_PRETTY: |
|
| 52 | - $items = (is_array($items) ? $items : iterator_to_array($items)); |
|
| 53 | - $output->writeln(json_encode($items, JSON_PRETTY_PRINT)); |
|
| 54 | - break; |
|
| 55 | - default: |
|
| 56 | - foreach ($items as $key => $item) { |
|
| 57 | - if (is_iterable($item)) { |
|
| 58 | - $output->writeln($prefix . $key . ':'); |
|
| 59 | - $this->writeArrayInOutputFormat($input, $output, $item, ' ' . $prefix); |
|
| 60 | - continue; |
|
| 61 | - } |
|
| 62 | - if (!is_int($key) || get_class($this) === ListCommand::class) { |
|
| 63 | - $value = $this->valueToString($item); |
|
| 64 | - if (!is_null($value)) { |
|
| 65 | - $output->writeln($prefix . $key . ': ' . $value); |
|
| 66 | - } else { |
|
| 67 | - $output->writeln($prefix . $key); |
|
| 68 | - } |
|
| 69 | - } else { |
|
| 70 | - $output->writeln($prefix . $this->valueToString($item)); |
|
| 71 | - } |
|
| 72 | - } |
|
| 73 | - break; |
|
| 74 | - } |
|
| 75 | - } |
|
| 76 | - |
|
| 77 | - protected function writeTableInOutputFormat(InputInterface $input, OutputInterface $output, array $items): void { |
|
| 78 | - switch ($input->getOption('output')) { |
|
| 79 | - case self::OUTPUT_FORMAT_JSON: |
|
| 80 | - $output->writeln(json_encode($items)); |
|
| 81 | - break; |
|
| 82 | - case self::OUTPUT_FORMAT_JSON_PRETTY: |
|
| 83 | - $output->writeln(json_encode($items, JSON_PRETTY_PRINT)); |
|
| 84 | - break; |
|
| 85 | - default: |
|
| 86 | - $table = new Table($output); |
|
| 87 | - $table->setRows($items); |
|
| 88 | - if (!empty($items) && is_string(array_key_first(reset($items)))) { |
|
| 89 | - $table->setHeaders(array_keys(reset($items))); |
|
| 90 | - } |
|
| 91 | - $table->render(); |
|
| 92 | - break; |
|
| 93 | - } |
|
| 94 | - } |
|
| 95 | - |
|
| 96 | - protected function writeStreamingTableInOutputFormat(InputInterface $input, OutputInterface $output, \Iterator $items, int $tableGroupSize): void { |
|
| 97 | - switch ($input->getOption('output')) { |
|
| 98 | - case self::OUTPUT_FORMAT_JSON: |
|
| 99 | - case self::OUTPUT_FORMAT_JSON_PRETTY: |
|
| 100 | - $this->writeStreamingJsonArray($input, $output, $items); |
|
| 101 | - break; |
|
| 102 | - default: |
|
| 103 | - foreach ($this->chunkIterator($items, $tableGroupSize) as $chunk) { |
|
| 104 | - $this->writeTableInOutputFormat($input, $output, $chunk); |
|
| 105 | - } |
|
| 106 | - break; |
|
| 107 | - } |
|
| 108 | - } |
|
| 109 | - |
|
| 110 | - protected function writeStreamingJsonArray(InputInterface $input, OutputInterface $output, \Iterator $items): void { |
|
| 111 | - $first = true; |
|
| 112 | - $outputType = $input->getOption('output'); |
|
| 113 | - |
|
| 114 | - $output->writeln('['); |
|
| 115 | - foreach ($items as $item) { |
|
| 116 | - if (!$first) { |
|
| 117 | - $output->writeln(','); |
|
| 118 | - } |
|
| 119 | - if ($outputType === self::OUTPUT_FORMAT_JSON_PRETTY) { |
|
| 120 | - $output->write(json_encode($item, JSON_PRETTY_PRINT)); |
|
| 121 | - } else { |
|
| 122 | - $output->write(json_encode($item)); |
|
| 123 | - } |
|
| 124 | - $first = false; |
|
| 125 | - } |
|
| 126 | - $output->writeln("\n]"); |
|
| 127 | - } |
|
| 128 | - |
|
| 129 | - public function chunkIterator(\Iterator $iterator, int $count): \Iterator { |
|
| 130 | - $chunk = []; |
|
| 131 | - |
|
| 132 | - for ($i = 0; $iterator->valid(); $i++) { |
|
| 133 | - $chunk[] = $iterator->current(); |
|
| 134 | - $iterator->next(); |
|
| 135 | - if (count($chunk) == $count) { |
|
| 136 | - // Got a full chunk, yield and start a new one |
|
| 137 | - yield $chunk; |
|
| 138 | - $chunk = []; |
|
| 139 | - } |
|
| 140 | - } |
|
| 141 | - |
|
| 142 | - if (count($chunk)) { |
|
| 143 | - // Yield the last chunk even if incomplete |
|
| 144 | - yield $chunk; |
|
| 145 | - } |
|
| 146 | - } |
|
| 147 | - |
|
| 148 | - |
|
| 149 | - /** |
|
| 150 | - * @param mixed $item |
|
| 151 | - */ |
|
| 152 | - protected function writeMixedInOutputFormat(InputInterface $input, OutputInterface $output, $item) { |
|
| 153 | - if (is_array($item)) { |
|
| 154 | - $this->writeArrayInOutputFormat($input, $output, $item, ''); |
|
| 155 | - return; |
|
| 156 | - } |
|
| 157 | - |
|
| 158 | - switch ($input->getOption('output')) { |
|
| 159 | - case self::OUTPUT_FORMAT_JSON: |
|
| 160 | - $output->writeln(json_encode($item)); |
|
| 161 | - break; |
|
| 162 | - case self::OUTPUT_FORMAT_JSON_PRETTY: |
|
| 163 | - $output->writeln(json_encode($item, JSON_PRETTY_PRINT)); |
|
| 164 | - break; |
|
| 165 | - default: |
|
| 166 | - $output->writeln($this->valueToString($item, false)); |
|
| 167 | - break; |
|
| 168 | - } |
|
| 169 | - } |
|
| 170 | - |
|
| 171 | - protected function valueToString($value, bool $returnNull = true): ?string { |
|
| 172 | - if ($value === false) { |
|
| 173 | - return 'false'; |
|
| 174 | - } elseif ($value === true) { |
|
| 175 | - return 'true'; |
|
| 176 | - } elseif ($value === null) { |
|
| 177 | - return $returnNull ? null : 'null'; |
|
| 178 | - } if ($value instanceof \UnitEnum) { |
|
| 179 | - return $value->value; |
|
| 180 | - } else { |
|
| 181 | - return $value; |
|
| 182 | - } |
|
| 183 | - } |
|
| 184 | - |
|
| 185 | - /** |
|
| 186 | - * Throw InterruptedException when interrupted by user |
|
| 187 | - * |
|
| 188 | - * @throws InterruptedException |
|
| 189 | - */ |
|
| 190 | - protected function abortIfInterrupted() { |
|
| 191 | - if ($this->php_pcntl_signal === false) { |
|
| 192 | - return; |
|
| 193 | - } |
|
| 194 | - |
|
| 195 | - pcntl_signal_dispatch(); |
|
| 196 | - |
|
| 197 | - if ($this->interrupted === true) { |
|
| 198 | - throw new InterruptedException('Command interrupted by user'); |
|
| 199 | - } |
|
| 200 | - } |
|
| 201 | - |
|
| 202 | - /** |
|
| 203 | - * Changes the status of the command to "interrupted" if ctrl-c has been pressed |
|
| 204 | - * |
|
| 205 | - * Gives a chance to the command to properly terminate what it's doing |
|
| 206 | - */ |
|
| 207 | - public function cancelOperation(): void { |
|
| 208 | - $this->interrupted = true; |
|
| 209 | - } |
|
| 210 | - |
|
| 211 | - public function run(InputInterface $input, OutputInterface $output): int { |
|
| 212 | - // check if the php pcntl_signal functions are accessible |
|
| 213 | - $this->php_pcntl_signal = function_exists('pcntl_signal'); |
|
| 214 | - if ($this->php_pcntl_signal) { |
|
| 215 | - // Collect interrupts and notify the running command |
|
| 216 | - pcntl_signal(SIGTERM, [$this, 'cancelOperation']); |
|
| 217 | - pcntl_signal(SIGINT, [$this, 'cancelOperation']); |
|
| 218 | - } |
|
| 219 | - |
|
| 220 | - return parent::run($input, $output); |
|
| 221 | - } |
|
| 222 | - |
|
| 223 | - /** |
|
| 224 | - * @param string $optionName |
|
| 225 | - * @param CompletionContext $context |
|
| 226 | - * @return string[] |
|
| 227 | - */ |
|
| 228 | - public function completeOptionValues($optionName, CompletionContext $context) { |
|
| 229 | - if ($optionName === 'output') { |
|
| 230 | - return ['plain', 'json', 'json_pretty']; |
|
| 231 | - } |
|
| 232 | - return []; |
|
| 233 | - } |
|
| 234 | - |
|
| 235 | - /** |
|
| 236 | - * @param string $argumentName |
|
| 237 | - * @param CompletionContext $context |
|
| 238 | - * @return string[] |
|
| 239 | - */ |
|
| 240 | - public function completeArgumentValues($argumentName, CompletionContext $context) { |
|
| 241 | - return []; |
|
| 242 | - } |
|
| 22 | + public const OUTPUT_FORMAT_PLAIN = 'plain'; |
|
| 23 | + public const OUTPUT_FORMAT_JSON = 'json'; |
|
| 24 | + public const OUTPUT_FORMAT_JSON_PRETTY = 'json_pretty'; |
|
| 25 | + |
|
| 26 | + protected string $defaultOutputFormat = self::OUTPUT_FORMAT_PLAIN; |
|
| 27 | + private bool $php_pcntl_signal = false; |
|
| 28 | + private bool $interrupted = false; |
|
| 29 | + |
|
| 30 | + protected function configure() { |
|
| 31 | + // Some of our commands do not extend this class; and some of those that do do not call parent::configure() |
|
| 32 | + $defaultHelp = 'More extensive and thorough documentation may be found at ' . Server::get(Defaults::class)->getDocBaseUrl() . PHP_EOL; |
|
| 33 | + $this |
|
| 34 | + ->setHelp($defaultHelp) |
|
| 35 | + ->addOption( |
|
| 36 | + 'output', |
|
| 37 | + null, |
|
| 38 | + InputOption::VALUE_OPTIONAL, |
|
| 39 | + 'Output format (plain, json or json_pretty, default is plain)', |
|
| 40 | + $this->defaultOutputFormat |
|
| 41 | + ) |
|
| 42 | + ; |
|
| 43 | + } |
|
| 44 | + |
|
| 45 | + protected function writeArrayInOutputFormat(InputInterface $input, OutputInterface $output, iterable $items, string $prefix = ' - '): void { |
|
| 46 | + switch ($input->getOption('output')) { |
|
| 47 | + case self::OUTPUT_FORMAT_JSON: |
|
| 48 | + $items = (is_array($items) ? $items : iterator_to_array($items)); |
|
| 49 | + $output->writeln(json_encode($items)); |
|
| 50 | + break; |
|
| 51 | + case self::OUTPUT_FORMAT_JSON_PRETTY: |
|
| 52 | + $items = (is_array($items) ? $items : iterator_to_array($items)); |
|
| 53 | + $output->writeln(json_encode($items, JSON_PRETTY_PRINT)); |
|
| 54 | + break; |
|
| 55 | + default: |
|
| 56 | + foreach ($items as $key => $item) { |
|
| 57 | + if (is_iterable($item)) { |
|
| 58 | + $output->writeln($prefix . $key . ':'); |
|
| 59 | + $this->writeArrayInOutputFormat($input, $output, $item, ' ' . $prefix); |
|
| 60 | + continue; |
|
| 61 | + } |
|
| 62 | + if (!is_int($key) || get_class($this) === ListCommand::class) { |
|
| 63 | + $value = $this->valueToString($item); |
|
| 64 | + if (!is_null($value)) { |
|
| 65 | + $output->writeln($prefix . $key . ': ' . $value); |
|
| 66 | + } else { |
|
| 67 | + $output->writeln($prefix . $key); |
|
| 68 | + } |
|
| 69 | + } else { |
|
| 70 | + $output->writeln($prefix . $this->valueToString($item)); |
|
| 71 | + } |
|
| 72 | + } |
|
| 73 | + break; |
|
| 74 | + } |
|
| 75 | + } |
|
| 76 | + |
|
| 77 | + protected function writeTableInOutputFormat(InputInterface $input, OutputInterface $output, array $items): void { |
|
| 78 | + switch ($input->getOption('output')) { |
|
| 79 | + case self::OUTPUT_FORMAT_JSON: |
|
| 80 | + $output->writeln(json_encode($items)); |
|
| 81 | + break; |
|
| 82 | + case self::OUTPUT_FORMAT_JSON_PRETTY: |
|
| 83 | + $output->writeln(json_encode($items, JSON_PRETTY_PRINT)); |
|
| 84 | + break; |
|
| 85 | + default: |
|
| 86 | + $table = new Table($output); |
|
| 87 | + $table->setRows($items); |
|
| 88 | + if (!empty($items) && is_string(array_key_first(reset($items)))) { |
|
| 89 | + $table->setHeaders(array_keys(reset($items))); |
|
| 90 | + } |
|
| 91 | + $table->render(); |
|
| 92 | + break; |
|
| 93 | + } |
|
| 94 | + } |
|
| 95 | + |
|
| 96 | + protected function writeStreamingTableInOutputFormat(InputInterface $input, OutputInterface $output, \Iterator $items, int $tableGroupSize): void { |
|
| 97 | + switch ($input->getOption('output')) { |
|
| 98 | + case self::OUTPUT_FORMAT_JSON: |
|
| 99 | + case self::OUTPUT_FORMAT_JSON_PRETTY: |
|
| 100 | + $this->writeStreamingJsonArray($input, $output, $items); |
|
| 101 | + break; |
|
| 102 | + default: |
|
| 103 | + foreach ($this->chunkIterator($items, $tableGroupSize) as $chunk) { |
|
| 104 | + $this->writeTableInOutputFormat($input, $output, $chunk); |
|
| 105 | + } |
|
| 106 | + break; |
|
| 107 | + } |
|
| 108 | + } |
|
| 109 | + |
|
| 110 | + protected function writeStreamingJsonArray(InputInterface $input, OutputInterface $output, \Iterator $items): void { |
|
| 111 | + $first = true; |
|
| 112 | + $outputType = $input->getOption('output'); |
|
| 113 | + |
|
| 114 | + $output->writeln('['); |
|
| 115 | + foreach ($items as $item) { |
|
| 116 | + if (!$first) { |
|
| 117 | + $output->writeln(','); |
|
| 118 | + } |
|
| 119 | + if ($outputType === self::OUTPUT_FORMAT_JSON_PRETTY) { |
|
| 120 | + $output->write(json_encode($item, JSON_PRETTY_PRINT)); |
|
| 121 | + } else { |
|
| 122 | + $output->write(json_encode($item)); |
|
| 123 | + } |
|
| 124 | + $first = false; |
|
| 125 | + } |
|
| 126 | + $output->writeln("\n]"); |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + public function chunkIterator(\Iterator $iterator, int $count): \Iterator { |
|
| 130 | + $chunk = []; |
|
| 131 | + |
|
| 132 | + for ($i = 0; $iterator->valid(); $i++) { |
|
| 133 | + $chunk[] = $iterator->current(); |
|
| 134 | + $iterator->next(); |
|
| 135 | + if (count($chunk) == $count) { |
|
| 136 | + // Got a full chunk, yield and start a new one |
|
| 137 | + yield $chunk; |
|
| 138 | + $chunk = []; |
|
| 139 | + } |
|
| 140 | + } |
|
| 141 | + |
|
| 142 | + if (count($chunk)) { |
|
| 143 | + // Yield the last chunk even if incomplete |
|
| 144 | + yield $chunk; |
|
| 145 | + } |
|
| 146 | + } |
|
| 147 | + |
|
| 148 | + |
|
| 149 | + /** |
|
| 150 | + * @param mixed $item |
|
| 151 | + */ |
|
| 152 | + protected function writeMixedInOutputFormat(InputInterface $input, OutputInterface $output, $item) { |
|
| 153 | + if (is_array($item)) { |
|
| 154 | + $this->writeArrayInOutputFormat($input, $output, $item, ''); |
|
| 155 | + return; |
|
| 156 | + } |
|
| 157 | + |
|
| 158 | + switch ($input->getOption('output')) { |
|
| 159 | + case self::OUTPUT_FORMAT_JSON: |
|
| 160 | + $output->writeln(json_encode($item)); |
|
| 161 | + break; |
|
| 162 | + case self::OUTPUT_FORMAT_JSON_PRETTY: |
|
| 163 | + $output->writeln(json_encode($item, JSON_PRETTY_PRINT)); |
|
| 164 | + break; |
|
| 165 | + default: |
|
| 166 | + $output->writeln($this->valueToString($item, false)); |
|
| 167 | + break; |
|
| 168 | + } |
|
| 169 | + } |
|
| 170 | + |
|
| 171 | + protected function valueToString($value, bool $returnNull = true): ?string { |
|
| 172 | + if ($value === false) { |
|
| 173 | + return 'false'; |
|
| 174 | + } elseif ($value === true) { |
|
| 175 | + return 'true'; |
|
| 176 | + } elseif ($value === null) { |
|
| 177 | + return $returnNull ? null : 'null'; |
|
| 178 | + } if ($value instanceof \UnitEnum) { |
|
| 179 | + return $value->value; |
|
| 180 | + } else { |
|
| 181 | + return $value; |
|
| 182 | + } |
|
| 183 | + } |
|
| 184 | + |
|
| 185 | + /** |
|
| 186 | + * Throw InterruptedException when interrupted by user |
|
| 187 | + * |
|
| 188 | + * @throws InterruptedException |
|
| 189 | + */ |
|
| 190 | + protected function abortIfInterrupted() { |
|
| 191 | + if ($this->php_pcntl_signal === false) { |
|
| 192 | + return; |
|
| 193 | + } |
|
| 194 | + |
|
| 195 | + pcntl_signal_dispatch(); |
|
| 196 | + |
|
| 197 | + if ($this->interrupted === true) { |
|
| 198 | + throw new InterruptedException('Command interrupted by user'); |
|
| 199 | + } |
|
| 200 | + } |
|
| 201 | + |
|
| 202 | + /** |
|
| 203 | + * Changes the status of the command to "interrupted" if ctrl-c has been pressed |
|
| 204 | + * |
|
| 205 | + * Gives a chance to the command to properly terminate what it's doing |
|
| 206 | + */ |
|
| 207 | + public function cancelOperation(): void { |
|
| 208 | + $this->interrupted = true; |
|
| 209 | + } |
|
| 210 | + |
|
| 211 | + public function run(InputInterface $input, OutputInterface $output): int { |
|
| 212 | + // check if the php pcntl_signal functions are accessible |
|
| 213 | + $this->php_pcntl_signal = function_exists('pcntl_signal'); |
|
| 214 | + if ($this->php_pcntl_signal) { |
|
| 215 | + // Collect interrupts and notify the running command |
|
| 216 | + pcntl_signal(SIGTERM, [$this, 'cancelOperation']); |
|
| 217 | + pcntl_signal(SIGINT, [$this, 'cancelOperation']); |
|
| 218 | + } |
|
| 219 | + |
|
| 220 | + return parent::run($input, $output); |
|
| 221 | + } |
|
| 222 | + |
|
| 223 | + /** |
|
| 224 | + * @param string $optionName |
|
| 225 | + * @param CompletionContext $context |
|
| 226 | + * @return string[] |
|
| 227 | + */ |
|
| 228 | + public function completeOptionValues($optionName, CompletionContext $context) { |
|
| 229 | + if ($optionName === 'output') { |
|
| 230 | + return ['plain', 'json', 'json_pretty']; |
|
| 231 | + } |
|
| 232 | + return []; |
|
| 233 | + } |
|
| 234 | + |
|
| 235 | + /** |
|
| 236 | + * @param string $argumentName |
|
| 237 | + * @param CompletionContext $context |
|
| 238 | + * @return string[] |
|
| 239 | + */ |
|
| 240 | + public function completeArgumentValues($argumentName, CompletionContext $context) { |
|
| 241 | + return []; |
|
| 242 | + } |
|
| 243 | 243 | } |
@@ -29,7 +29,7 @@ discard block |
||
| 29 | 29 | |
| 30 | 30 | protected function configure() { |
| 31 | 31 | // Some of our commands do not extend this class; and some of those that do do not call parent::configure() |
| 32 | - $defaultHelp = 'More extensive and thorough documentation may be found at ' . Server::get(Defaults::class)->getDocBaseUrl() . PHP_EOL; |
|
| 32 | + $defaultHelp = 'More extensive and thorough documentation may be found at '.Server::get(Defaults::class)->getDocBaseUrl().PHP_EOL; |
|
| 33 | 33 | $this |
| 34 | 34 | ->setHelp($defaultHelp) |
| 35 | 35 | ->addOption( |
@@ -55,19 +55,19 @@ discard block |
||
| 55 | 55 | default: |
| 56 | 56 | foreach ($items as $key => $item) { |
| 57 | 57 | if (is_iterable($item)) { |
| 58 | - $output->writeln($prefix . $key . ':'); |
|
| 59 | - $this->writeArrayInOutputFormat($input, $output, $item, ' ' . $prefix); |
|
| 58 | + $output->writeln($prefix.$key.':'); |
|
| 59 | + $this->writeArrayInOutputFormat($input, $output, $item, ' '.$prefix); |
|
| 60 | 60 | continue; |
| 61 | 61 | } |
| 62 | 62 | if (!is_int($key) || get_class($this) === ListCommand::class) { |
| 63 | 63 | $value = $this->valueToString($item); |
| 64 | 64 | if (!is_null($value)) { |
| 65 | - $output->writeln($prefix . $key . ': ' . $value); |
|
| 65 | + $output->writeln($prefix.$key.': '.$value); |
|
| 66 | 66 | } else { |
| 67 | - $output->writeln($prefix . $key); |
|
| 67 | + $output->writeln($prefix.$key); |
|
| 68 | 68 | } |
| 69 | 69 | } else { |
| 70 | - $output->writeln($prefix . $this->valueToString($item)); |
|
| 70 | + $output->writeln($prefix.$this->valueToString($item)); |
|
| 71 | 71 | } |
| 72 | 72 | } |
| 73 | 73 | break; |
@@ -31,232 +31,232 @@ |
||
| 31 | 31 | |
| 32 | 32 | #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] |
| 33 | 33 | class TwoFactorChallengeController extends Controller { |
| 34 | - public function __construct( |
|
| 35 | - string $appName, |
|
| 36 | - IRequest $request, |
|
| 37 | - private Manager $twoFactorManager, |
|
| 38 | - private IUserSession $userSession, |
|
| 39 | - private ISession $session, |
|
| 40 | - private IURLGenerator $urlGenerator, |
|
| 41 | - private LoggerInterface $logger, |
|
| 42 | - ) { |
|
| 43 | - parent::__construct($appName, $request); |
|
| 44 | - } |
|
| 34 | + public function __construct( |
|
| 35 | + string $appName, |
|
| 36 | + IRequest $request, |
|
| 37 | + private Manager $twoFactorManager, |
|
| 38 | + private IUserSession $userSession, |
|
| 39 | + private ISession $session, |
|
| 40 | + private IURLGenerator $urlGenerator, |
|
| 41 | + private LoggerInterface $logger, |
|
| 42 | + ) { |
|
| 43 | + parent::__construct($appName, $request); |
|
| 44 | + } |
|
| 45 | 45 | |
| 46 | - /** |
|
| 47 | - * @return string |
|
| 48 | - */ |
|
| 49 | - protected function getLogoutUrl() { |
|
| 50 | - return OC_User::getLogoutUrl($this->urlGenerator); |
|
| 51 | - } |
|
| 46 | + /** |
|
| 47 | + * @return string |
|
| 48 | + */ |
|
| 49 | + protected function getLogoutUrl() { |
|
| 50 | + return OC_User::getLogoutUrl($this->urlGenerator); |
|
| 51 | + } |
|
| 52 | 52 | |
| 53 | - /** |
|
| 54 | - * @param IProvider[] $providers |
|
| 55 | - */ |
|
| 56 | - private function splitProvidersAndBackupCodes(array $providers): array { |
|
| 57 | - $regular = []; |
|
| 58 | - $backup = null; |
|
| 59 | - foreach ($providers as $provider) { |
|
| 60 | - if ($provider->getId() === 'backup_codes') { |
|
| 61 | - $backup = $provider; |
|
| 62 | - } else { |
|
| 63 | - $regular[] = $provider; |
|
| 64 | - } |
|
| 65 | - } |
|
| 53 | + /** |
|
| 54 | + * @param IProvider[] $providers |
|
| 55 | + */ |
|
| 56 | + private function splitProvidersAndBackupCodes(array $providers): array { |
|
| 57 | + $regular = []; |
|
| 58 | + $backup = null; |
|
| 59 | + foreach ($providers as $provider) { |
|
| 60 | + if ($provider->getId() === 'backup_codes') { |
|
| 61 | + $backup = $provider; |
|
| 62 | + } else { |
|
| 63 | + $regular[] = $provider; |
|
| 64 | + } |
|
| 65 | + } |
|
| 66 | 66 | |
| 67 | - return [$regular, $backup]; |
|
| 68 | - } |
|
| 67 | + return [$regular, $backup]; |
|
| 68 | + } |
|
| 69 | 69 | |
| 70 | - /** |
|
| 71 | - * @TwoFactorSetUpDoneRequired |
|
| 72 | - * |
|
| 73 | - * @param string $redirect_url |
|
| 74 | - * @return StandaloneTemplateResponse |
|
| 75 | - */ |
|
| 76 | - #[NoAdminRequired] |
|
| 77 | - #[NoCSRFRequired] |
|
| 78 | - #[FrontpageRoute(verb: 'GET', url: '/login/selectchallenge')] |
|
| 79 | - public function selectChallenge($redirect_url) { |
|
| 80 | - $user = $this->userSession->getUser(); |
|
| 81 | - $providerSet = $this->twoFactorManager->getProviderSet($user); |
|
| 82 | - $allProviders = $providerSet->getProviders(); |
|
| 83 | - [$providers, $backupProvider] = $this->splitProvidersAndBackupCodes($allProviders); |
|
| 84 | - $setupProviders = $this->twoFactorManager->getLoginSetupProviders($user); |
|
| 70 | + /** |
|
| 71 | + * @TwoFactorSetUpDoneRequired |
|
| 72 | + * |
|
| 73 | + * @param string $redirect_url |
|
| 74 | + * @return StandaloneTemplateResponse |
|
| 75 | + */ |
|
| 76 | + #[NoAdminRequired] |
|
| 77 | + #[NoCSRFRequired] |
|
| 78 | + #[FrontpageRoute(verb: 'GET', url: '/login/selectchallenge')] |
|
| 79 | + public function selectChallenge($redirect_url) { |
|
| 80 | + $user = $this->userSession->getUser(); |
|
| 81 | + $providerSet = $this->twoFactorManager->getProviderSet($user); |
|
| 82 | + $allProviders = $providerSet->getProviders(); |
|
| 83 | + [$providers, $backupProvider] = $this->splitProvidersAndBackupCodes($allProviders); |
|
| 84 | + $setupProviders = $this->twoFactorManager->getLoginSetupProviders($user); |
|
| 85 | 85 | |
| 86 | - $data = [ |
|
| 87 | - 'providers' => $providers, |
|
| 88 | - 'backupProvider' => $backupProvider, |
|
| 89 | - 'providerMissing' => $providerSet->isProviderMissing(), |
|
| 90 | - 'redirect_url' => $redirect_url, |
|
| 91 | - 'logout_url' => $this->getLogoutUrl(), |
|
| 92 | - 'hasSetupProviders' => !empty($setupProviders), |
|
| 93 | - ]; |
|
| 94 | - Util::addScript('core', 'twofactor-request-token'); |
|
| 95 | - return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest'); |
|
| 96 | - } |
|
| 86 | + $data = [ |
|
| 87 | + 'providers' => $providers, |
|
| 88 | + 'backupProvider' => $backupProvider, |
|
| 89 | + 'providerMissing' => $providerSet->isProviderMissing(), |
|
| 90 | + 'redirect_url' => $redirect_url, |
|
| 91 | + 'logout_url' => $this->getLogoutUrl(), |
|
| 92 | + 'hasSetupProviders' => !empty($setupProviders), |
|
| 93 | + ]; |
|
| 94 | + Util::addScript('core', 'twofactor-request-token'); |
|
| 95 | + return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest'); |
|
| 96 | + } |
|
| 97 | 97 | |
| 98 | - /** |
|
| 99 | - * @TwoFactorSetUpDoneRequired |
|
| 100 | - * |
|
| 101 | - * @param string $challengeProviderId |
|
| 102 | - * @param string $redirect_url |
|
| 103 | - * @return StandaloneTemplateResponse|RedirectResponse |
|
| 104 | - */ |
|
| 105 | - #[NoAdminRequired] |
|
| 106 | - #[NoCSRFRequired] |
|
| 107 | - #[UseSession] |
|
| 108 | - #[FrontpageRoute(verb: 'GET', url: '/login/challenge/{challengeProviderId}')] |
|
| 109 | - public function showChallenge($challengeProviderId, $redirect_url) { |
|
| 110 | - $user = $this->userSession->getUser(); |
|
| 111 | - $providerSet = $this->twoFactorManager->getProviderSet($user); |
|
| 112 | - $provider = $providerSet->getProvider($challengeProviderId); |
|
| 98 | + /** |
|
| 99 | + * @TwoFactorSetUpDoneRequired |
|
| 100 | + * |
|
| 101 | + * @param string $challengeProviderId |
|
| 102 | + * @param string $redirect_url |
|
| 103 | + * @return StandaloneTemplateResponse|RedirectResponse |
|
| 104 | + */ |
|
| 105 | + #[NoAdminRequired] |
|
| 106 | + #[NoCSRFRequired] |
|
| 107 | + #[UseSession] |
|
| 108 | + #[FrontpageRoute(verb: 'GET', url: '/login/challenge/{challengeProviderId}')] |
|
| 109 | + public function showChallenge($challengeProviderId, $redirect_url) { |
|
| 110 | + $user = $this->userSession->getUser(); |
|
| 111 | + $providerSet = $this->twoFactorManager->getProviderSet($user); |
|
| 112 | + $provider = $providerSet->getProvider($challengeProviderId); |
|
| 113 | 113 | |
| 114 | - if (is_null($provider)) { |
|
| 115 | - return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); |
|
| 116 | - } |
|
| 114 | + if (is_null($provider)) { |
|
| 115 | + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); |
|
| 116 | + } |
|
| 117 | 117 | |
| 118 | - $backupProvider = $providerSet->getProvider('backup_codes'); |
|
| 119 | - if (!is_null($backupProvider) && $backupProvider->getId() === $provider->getId()) { |
|
| 120 | - // Don't show the backup provider link if we're already showing that provider's challenge |
|
| 121 | - $backupProvider = null; |
|
| 122 | - } |
|
| 118 | + $backupProvider = $providerSet->getProvider('backup_codes'); |
|
| 119 | + if (!is_null($backupProvider) && $backupProvider->getId() === $provider->getId()) { |
|
| 120 | + // Don't show the backup provider link if we're already showing that provider's challenge |
|
| 121 | + $backupProvider = null; |
|
| 122 | + } |
|
| 123 | 123 | |
| 124 | - $errorMessage = ''; |
|
| 125 | - $error = false; |
|
| 126 | - if ($this->session->exists('two_factor_auth_error')) { |
|
| 127 | - $this->session->remove('two_factor_auth_error'); |
|
| 128 | - $error = true; |
|
| 129 | - $errorMessage = $this->session->get('two_factor_auth_error_message'); |
|
| 130 | - $this->session->remove('two_factor_auth_error_message'); |
|
| 131 | - } |
|
| 132 | - $tmpl = $provider->getTemplate($user); |
|
| 133 | - $tmpl->assign('redirect_url', $redirect_url); |
|
| 134 | - $data = [ |
|
| 135 | - 'error' => $error, |
|
| 136 | - 'error_message' => $errorMessage, |
|
| 137 | - 'provider' => $provider, |
|
| 138 | - 'backupProvider' => $backupProvider, |
|
| 139 | - 'logout_url' => $this->getLogoutUrl(), |
|
| 140 | - 'redirect_url' => $redirect_url, |
|
| 141 | - 'template' => $tmpl->fetchPage(), |
|
| 142 | - ]; |
|
| 143 | - $response = new StandaloneTemplateResponse($this->appName, 'twofactorshowchallenge', $data, 'guest'); |
|
| 144 | - if ($provider instanceof IProvidesCustomCSP) { |
|
| 145 | - $response->setContentSecurityPolicy($provider->getCSP()); |
|
| 146 | - } |
|
| 147 | - Util::addScript('core', 'twofactor-request-token'); |
|
| 148 | - return $response; |
|
| 149 | - } |
|
| 124 | + $errorMessage = ''; |
|
| 125 | + $error = false; |
|
| 126 | + if ($this->session->exists('two_factor_auth_error')) { |
|
| 127 | + $this->session->remove('two_factor_auth_error'); |
|
| 128 | + $error = true; |
|
| 129 | + $errorMessage = $this->session->get('two_factor_auth_error_message'); |
|
| 130 | + $this->session->remove('two_factor_auth_error_message'); |
|
| 131 | + } |
|
| 132 | + $tmpl = $provider->getTemplate($user); |
|
| 133 | + $tmpl->assign('redirect_url', $redirect_url); |
|
| 134 | + $data = [ |
|
| 135 | + 'error' => $error, |
|
| 136 | + 'error_message' => $errorMessage, |
|
| 137 | + 'provider' => $provider, |
|
| 138 | + 'backupProvider' => $backupProvider, |
|
| 139 | + 'logout_url' => $this->getLogoutUrl(), |
|
| 140 | + 'redirect_url' => $redirect_url, |
|
| 141 | + 'template' => $tmpl->fetchPage(), |
|
| 142 | + ]; |
|
| 143 | + $response = new StandaloneTemplateResponse($this->appName, 'twofactorshowchallenge', $data, 'guest'); |
|
| 144 | + if ($provider instanceof IProvidesCustomCSP) { |
|
| 145 | + $response->setContentSecurityPolicy($provider->getCSP()); |
|
| 146 | + } |
|
| 147 | + Util::addScript('core', 'twofactor-request-token'); |
|
| 148 | + return $response; |
|
| 149 | + } |
|
| 150 | 150 | |
| 151 | - /** |
|
| 152 | - * @TwoFactorSetUpDoneRequired |
|
| 153 | - * |
|
| 154 | - * |
|
| 155 | - * @param string $challengeProviderId |
|
| 156 | - * @param string $challenge |
|
| 157 | - * @param string $redirect_url |
|
| 158 | - * @return RedirectResponse |
|
| 159 | - */ |
|
| 160 | - #[NoAdminRequired] |
|
| 161 | - #[NoCSRFRequired] |
|
| 162 | - #[UseSession] |
|
| 163 | - #[FrontpageRoute(verb: 'POST', url: '/login/challenge/{challengeProviderId}')] |
|
| 164 | - #[UserRateLimit(limit: 5, period: 100)] |
|
| 165 | - public function solveChallenge($challengeProviderId, $challenge, $redirect_url = null) { |
|
| 166 | - $user = $this->userSession->getUser(); |
|
| 167 | - $provider = $this->twoFactorManager->getProvider($user, $challengeProviderId); |
|
| 168 | - if (is_null($provider)) { |
|
| 169 | - return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); |
|
| 170 | - } |
|
| 151 | + /** |
|
| 152 | + * @TwoFactorSetUpDoneRequired |
|
| 153 | + * |
|
| 154 | + * |
|
| 155 | + * @param string $challengeProviderId |
|
| 156 | + * @param string $challenge |
|
| 157 | + * @param string $redirect_url |
|
| 158 | + * @return RedirectResponse |
|
| 159 | + */ |
|
| 160 | + #[NoAdminRequired] |
|
| 161 | + #[NoCSRFRequired] |
|
| 162 | + #[UseSession] |
|
| 163 | + #[FrontpageRoute(verb: 'POST', url: '/login/challenge/{challengeProviderId}')] |
|
| 164 | + #[UserRateLimit(limit: 5, period: 100)] |
|
| 165 | + public function solveChallenge($challengeProviderId, $challenge, $redirect_url = null) { |
|
| 166 | + $user = $this->userSession->getUser(); |
|
| 167 | + $provider = $this->twoFactorManager->getProvider($user, $challengeProviderId); |
|
| 168 | + if (is_null($provider)) { |
|
| 169 | + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); |
|
| 170 | + } |
|
| 171 | 171 | |
| 172 | - try { |
|
| 173 | - if ($this->twoFactorManager->verifyChallenge($challengeProviderId, $user, $challenge)) { |
|
| 174 | - if (!is_null($redirect_url)) { |
|
| 175 | - return new RedirectResponse($this->urlGenerator->getAbsoluteURL(urldecode($redirect_url))); |
|
| 176 | - } |
|
| 177 | - return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl()); |
|
| 178 | - } |
|
| 179 | - } catch (TwoFactorException $e) { |
|
| 180 | - /* |
|
| 172 | + try { |
|
| 173 | + if ($this->twoFactorManager->verifyChallenge($challengeProviderId, $user, $challenge)) { |
|
| 174 | + if (!is_null($redirect_url)) { |
|
| 175 | + return new RedirectResponse($this->urlGenerator->getAbsoluteURL(urldecode($redirect_url))); |
|
| 176 | + } |
|
| 177 | + return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl()); |
|
| 178 | + } |
|
| 179 | + } catch (TwoFactorException $e) { |
|
| 180 | + /* |
|
| 181 | 181 | * The 2FA App threw an TwoFactorException. Now we display more |
| 182 | 182 | * information to the user. The exception text is stored in the |
| 183 | 183 | * session to be used in showChallenge() |
| 184 | 184 | */ |
| 185 | - $this->session->set('two_factor_auth_error_message', $e->getMessage()); |
|
| 186 | - } |
|
| 185 | + $this->session->set('two_factor_auth_error_message', $e->getMessage()); |
|
| 186 | + } |
|
| 187 | 187 | |
| 188 | - $ip = $this->request->getRemoteAddress(); |
|
| 189 | - $uid = $user->getUID(); |
|
| 190 | - $this->logger->warning("Two-factor challenge failed: $uid (Remote IP: $ip)"); |
|
| 191 | - $this->session->set('two_factor_auth_error', true); |
|
| 192 | - return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.showChallenge', [ |
|
| 193 | - 'challengeProviderId' => $provider->getId(), |
|
| 194 | - 'redirect_url' => $redirect_url, |
|
| 195 | - ])); |
|
| 196 | - } |
|
| 188 | + $ip = $this->request->getRemoteAddress(); |
|
| 189 | + $uid = $user->getUID(); |
|
| 190 | + $this->logger->warning("Two-factor challenge failed: $uid (Remote IP: $ip)"); |
|
| 191 | + $this->session->set('two_factor_auth_error', true); |
|
| 192 | + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.showChallenge', [ |
|
| 193 | + 'challengeProviderId' => $provider->getId(), |
|
| 194 | + 'redirect_url' => $redirect_url, |
|
| 195 | + ])); |
|
| 196 | + } |
|
| 197 | 197 | |
| 198 | - #[NoAdminRequired] |
|
| 199 | - #[NoCSRFRequired] |
|
| 200 | - #[FrontpageRoute(verb: 'GET', url: 'login/setupchallenge')] |
|
| 201 | - public function setupProviders(?string $redirect_url = null): StandaloneTemplateResponse { |
|
| 202 | - $user = $this->userSession->getUser(); |
|
| 203 | - $setupProviders = $this->twoFactorManager->getLoginSetupProviders($user); |
|
| 198 | + #[NoAdminRequired] |
|
| 199 | + #[NoCSRFRequired] |
|
| 200 | + #[FrontpageRoute(verb: 'GET', url: 'login/setupchallenge')] |
|
| 201 | + public function setupProviders(?string $redirect_url = null): StandaloneTemplateResponse { |
|
| 202 | + $user = $this->userSession->getUser(); |
|
| 203 | + $setupProviders = $this->twoFactorManager->getLoginSetupProviders($user); |
|
| 204 | 204 | |
| 205 | - $data = [ |
|
| 206 | - 'providers' => $setupProviders, |
|
| 207 | - 'logout_url' => $this->getLogoutUrl(), |
|
| 208 | - 'redirect_url' => $redirect_url, |
|
| 209 | - ]; |
|
| 205 | + $data = [ |
|
| 206 | + 'providers' => $setupProviders, |
|
| 207 | + 'logout_url' => $this->getLogoutUrl(), |
|
| 208 | + 'redirect_url' => $redirect_url, |
|
| 209 | + ]; |
|
| 210 | 210 | |
| 211 | - Util::addScript('core', 'twofactor-request-token'); |
|
| 212 | - return new StandaloneTemplateResponse($this->appName, 'twofactorsetupselection', $data, 'guest'); |
|
| 213 | - } |
|
| 211 | + Util::addScript('core', 'twofactor-request-token'); |
|
| 212 | + return new StandaloneTemplateResponse($this->appName, 'twofactorsetupselection', $data, 'guest'); |
|
| 213 | + } |
|
| 214 | 214 | |
| 215 | - #[NoAdminRequired] |
|
| 216 | - #[NoCSRFRequired] |
|
| 217 | - #[FrontpageRoute(verb: 'GET', url: 'login/setupchallenge/{providerId}')] |
|
| 218 | - public function setupProvider(string $providerId, ?string $redirect_url = null) { |
|
| 219 | - $user = $this->userSession->getUser(); |
|
| 220 | - $providers = $this->twoFactorManager->getLoginSetupProviders($user); |
|
| 215 | + #[NoAdminRequired] |
|
| 216 | + #[NoCSRFRequired] |
|
| 217 | + #[FrontpageRoute(verb: 'GET', url: 'login/setupchallenge/{providerId}')] |
|
| 218 | + public function setupProvider(string $providerId, ?string $redirect_url = null) { |
|
| 219 | + $user = $this->userSession->getUser(); |
|
| 220 | + $providers = $this->twoFactorManager->getLoginSetupProviders($user); |
|
| 221 | 221 | |
| 222 | - $provider = null; |
|
| 223 | - foreach ($providers as $p) { |
|
| 224 | - if ($p->getId() === $providerId) { |
|
| 225 | - $provider = $p; |
|
| 226 | - break; |
|
| 227 | - } |
|
| 228 | - } |
|
| 222 | + $provider = null; |
|
| 223 | + foreach ($providers as $p) { |
|
| 224 | + if ($p->getId() === $providerId) { |
|
| 225 | + $provider = $p; |
|
| 226 | + break; |
|
| 227 | + } |
|
| 228 | + } |
|
| 229 | 229 | |
| 230 | - if ($provider === null) { |
|
| 231 | - return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); |
|
| 232 | - } |
|
| 230 | + if ($provider === null) { |
|
| 231 | + return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge')); |
|
| 232 | + } |
|
| 233 | 233 | |
| 234 | - /** @var IActivatableAtLogin $provider */ |
|
| 235 | - $tmpl = $provider->getLoginSetup($user)->getBody(); |
|
| 236 | - $data = [ |
|
| 237 | - 'provider' => $provider, |
|
| 238 | - 'logout_url' => $this->getLogoutUrl(), |
|
| 239 | - 'redirect_url' => $redirect_url, |
|
| 240 | - 'template' => $tmpl->fetchPage(), |
|
| 241 | - ]; |
|
| 242 | - $response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupchallenge', $data, 'guest'); |
|
| 243 | - Util::addScript('core', 'twofactor-request-token'); |
|
| 244 | - return $response; |
|
| 245 | - } |
|
| 234 | + /** @var IActivatableAtLogin $provider */ |
|
| 235 | + $tmpl = $provider->getLoginSetup($user)->getBody(); |
|
| 236 | + $data = [ |
|
| 237 | + 'provider' => $provider, |
|
| 238 | + 'logout_url' => $this->getLogoutUrl(), |
|
| 239 | + 'redirect_url' => $redirect_url, |
|
| 240 | + 'template' => $tmpl->fetchPage(), |
|
| 241 | + ]; |
|
| 242 | + $response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupchallenge', $data, 'guest'); |
|
| 243 | + Util::addScript('core', 'twofactor-request-token'); |
|
| 244 | + return $response; |
|
| 245 | + } |
|
| 246 | 246 | |
| 247 | - /** |
|
| 248 | - * @todo handle the extreme edge case of an invalid provider ID and redirect to the provider selection page |
|
| 249 | - */ |
|
| 250 | - #[NoAdminRequired] |
|
| 251 | - #[NoCSRFRequired] |
|
| 252 | - #[FrontpageRoute(verb: 'POST', url: 'login/setupchallenge/{providerId}')] |
|
| 253 | - public function confirmProviderSetup(string $providerId, ?string $redirect_url = null) { |
|
| 254 | - return new RedirectResponse($this->urlGenerator->linkToRoute( |
|
| 255 | - 'core.TwoFactorChallenge.showChallenge', |
|
| 256 | - [ |
|
| 257 | - 'challengeProviderId' => $providerId, |
|
| 258 | - 'redirect_url' => $redirect_url, |
|
| 259 | - ] |
|
| 260 | - )); |
|
| 261 | - } |
|
| 247 | + /** |
|
| 248 | + * @todo handle the extreme edge case of an invalid provider ID and redirect to the provider selection page |
|
| 249 | + */ |
|
| 250 | + #[NoAdminRequired] |
|
| 251 | + #[NoCSRFRequired] |
|
| 252 | + #[FrontpageRoute(verb: 'POST', url: 'login/setupchallenge/{providerId}')] |
|
| 253 | + public function confirmProviderSetup(string $providerId, ?string $redirect_url = null) { |
|
| 254 | + return new RedirectResponse($this->urlGenerator->linkToRoute( |
|
| 255 | + 'core.TwoFactorChallenge.showChallenge', |
|
| 256 | + [ |
|
| 257 | + 'challengeProviderId' => $providerId, |
|
| 258 | + 'redirect_url' => $redirect_url, |
|
| 259 | + ] |
|
| 260 | + )); |
|
| 261 | + } |
|
| 262 | 262 | } |
@@ -15,49 +15,49 @@ |
||
| 15 | 15 | use OCP\IRequest; |
| 16 | 16 | |
| 17 | 17 | class AuthorizedGroupController extends Controller { |
| 18 | - public function __construct( |
|
| 19 | - string $appName, |
|
| 20 | - IRequest $request, |
|
| 21 | - private AuthorizedGroupService $authorizedGroupService, |
|
| 22 | - ) { |
|
| 23 | - parent::__construct($appName, $request); |
|
| 24 | - } |
|
| 18 | + public function __construct( |
|
| 19 | + string $appName, |
|
| 20 | + IRequest $request, |
|
| 21 | + private AuthorizedGroupService $authorizedGroupService, |
|
| 22 | + ) { |
|
| 23 | + parent::__construct($appName, $request); |
|
| 24 | + } |
|
| 25 | 25 | |
| 26 | - /** |
|
| 27 | - * @throws NotFoundException |
|
| 28 | - * @throws Exception |
|
| 29 | - */ |
|
| 30 | - public function saveSettings(array $newGroups, string $class): DataResponse { |
|
| 31 | - $currentGroups = $this->authorizedGroupService->findExistingGroupsForClass($class); |
|
| 26 | + /** |
|
| 27 | + * @throws NotFoundException |
|
| 28 | + * @throws Exception |
|
| 29 | + */ |
|
| 30 | + public function saveSettings(array $newGroups, string $class): DataResponse { |
|
| 31 | + $currentGroups = $this->authorizedGroupService->findExistingGroupsForClass($class); |
|
| 32 | 32 | |
| 33 | - foreach ($currentGroups as $group) { |
|
| 34 | - /** @var AuthorizedGroup $group */ |
|
| 35 | - $removed = true; |
|
| 36 | - foreach ($newGroups as $groupData) { |
|
| 37 | - if ($groupData['gid'] === $group->getGroupId()) { |
|
| 38 | - $removed = false; |
|
| 39 | - break; |
|
| 40 | - } |
|
| 41 | - } |
|
| 42 | - if ($removed) { |
|
| 43 | - $this->authorizedGroupService->delete($group->getId()); |
|
| 44 | - } |
|
| 45 | - } |
|
| 33 | + foreach ($currentGroups as $group) { |
|
| 34 | + /** @var AuthorizedGroup $group */ |
|
| 35 | + $removed = true; |
|
| 36 | + foreach ($newGroups as $groupData) { |
|
| 37 | + if ($groupData['gid'] === $group->getGroupId()) { |
|
| 38 | + $removed = false; |
|
| 39 | + break; |
|
| 40 | + } |
|
| 41 | + } |
|
| 42 | + if ($removed) { |
|
| 43 | + $this->authorizedGroupService->delete($group->getId()); |
|
| 44 | + } |
|
| 45 | + } |
|
| 46 | 46 | |
| 47 | - foreach ($newGroups as $groupData) { |
|
| 48 | - $added = true; |
|
| 49 | - foreach ($currentGroups as $group) { |
|
| 50 | - /** @var AuthorizedGroup $group */ |
|
| 51 | - if ($groupData['gid'] === $group->getGroupId()) { |
|
| 52 | - $added = false; |
|
| 53 | - break; |
|
| 54 | - } |
|
| 55 | - } |
|
| 56 | - if ($added) { |
|
| 57 | - $this->authorizedGroupService->create($groupData['gid'], $class); |
|
| 58 | - } |
|
| 59 | - } |
|
| 47 | + foreach ($newGroups as $groupData) { |
|
| 48 | + $added = true; |
|
| 49 | + foreach ($currentGroups as $group) { |
|
| 50 | + /** @var AuthorizedGroup $group */ |
|
| 51 | + if ($groupData['gid'] === $group->getGroupId()) { |
|
| 52 | + $added = false; |
|
| 53 | + break; |
|
| 54 | + } |
|
| 55 | + } |
|
| 56 | + if ($added) { |
|
| 57 | + $this->authorizedGroupService->create($groupData['gid'], $class); |
|
| 58 | + } |
|
| 59 | + } |
|
| 60 | 60 | |
| 61 | - return new DataResponse(['valid' => true]); |
|
| 62 | - } |
|
| 61 | + return new DataResponse(['valid' => true]); |
|
| 62 | + } |
|
| 63 | 63 | } |
@@ -27,59 +27,59 @@ discard block |
||
| 27 | 27 | |
| 28 | 28 | #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] |
| 29 | 29 | class CheckSetupController extends Controller { |
| 30 | - /** @var Checker */ |
|
| 31 | - private $checker; |
|
| 32 | - |
|
| 33 | - public function __construct( |
|
| 34 | - $appName, |
|
| 35 | - IRequest $request, |
|
| 36 | - private IConfig $config, |
|
| 37 | - private IURLGenerator $urlGenerator, |
|
| 38 | - private IL10N $l10n, |
|
| 39 | - Checker $checker, |
|
| 40 | - private LoggerInterface $logger, |
|
| 41 | - private ISetupCheckManager $setupCheckManager, |
|
| 42 | - ) { |
|
| 43 | - parent::__construct($appName, $request); |
|
| 44 | - $this->checker = $checker; |
|
| 45 | - } |
|
| 46 | - |
|
| 47 | - /** |
|
| 48 | - * @return DataResponse |
|
| 49 | - */ |
|
| 50 | - #[NoCSRFRequired] |
|
| 51 | - #[NoAdminRequired] |
|
| 52 | - public function setupCheckManager(): DataResponse { |
|
| 53 | - return new DataResponse($this->setupCheckManager->runAll()); |
|
| 54 | - } |
|
| 55 | - |
|
| 56 | - /** |
|
| 57 | - * @return RedirectResponse |
|
| 58 | - */ |
|
| 59 | - #[NoCSRFRequired] |
|
| 60 | - #[AuthorizedAdminSetting(settings: Overview::class)] |
|
| 61 | - public function rescanFailedIntegrityCheck(): RedirectResponse { |
|
| 62 | - $this->checker->runInstanceVerification(); |
|
| 63 | - return new RedirectResponse( |
|
| 64 | - $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview']) |
|
| 65 | - ); |
|
| 66 | - } |
|
| 67 | - |
|
| 68 | - #[NoCSRFRequired] |
|
| 69 | - #[AuthorizedAdminSetting(settings: Overview::class)] |
|
| 70 | - public function getFailedIntegrityCheckFiles(): DataDisplayResponse { |
|
| 71 | - if (!$this->checker->isCodeCheckEnforced()) { |
|
| 72 | - return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.'); |
|
| 73 | - } |
|
| 74 | - |
|
| 75 | - $completeResults = $this->checker->getResults(); |
|
| 76 | - |
|
| 77 | - if ($completeResults === null) { |
|
| 78 | - return new DataDisplayResponse('Integrity checker has not been run. Integrity information not available.'); |
|
| 79 | - } |
|
| 80 | - |
|
| 81 | - if (!empty($completeResults)) { |
|
| 82 | - $formattedTextResponse = 'Technical information |
|
| 30 | + /** @var Checker */ |
|
| 31 | + private $checker; |
|
| 32 | + |
|
| 33 | + public function __construct( |
|
| 34 | + $appName, |
|
| 35 | + IRequest $request, |
|
| 36 | + private IConfig $config, |
|
| 37 | + private IURLGenerator $urlGenerator, |
|
| 38 | + private IL10N $l10n, |
|
| 39 | + Checker $checker, |
|
| 40 | + private LoggerInterface $logger, |
|
| 41 | + private ISetupCheckManager $setupCheckManager, |
|
| 42 | + ) { |
|
| 43 | + parent::__construct($appName, $request); |
|
| 44 | + $this->checker = $checker; |
|
| 45 | + } |
|
| 46 | + |
|
| 47 | + /** |
|
| 48 | + * @return DataResponse |
|
| 49 | + */ |
|
| 50 | + #[NoCSRFRequired] |
|
| 51 | + #[NoAdminRequired] |
|
| 52 | + public function setupCheckManager(): DataResponse { |
|
| 53 | + return new DataResponse($this->setupCheckManager->runAll()); |
|
| 54 | + } |
|
| 55 | + |
|
| 56 | + /** |
|
| 57 | + * @return RedirectResponse |
|
| 58 | + */ |
|
| 59 | + #[NoCSRFRequired] |
|
| 60 | + #[AuthorizedAdminSetting(settings: Overview::class)] |
|
| 61 | + public function rescanFailedIntegrityCheck(): RedirectResponse { |
|
| 62 | + $this->checker->runInstanceVerification(); |
|
| 63 | + return new RedirectResponse( |
|
| 64 | + $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview']) |
|
| 65 | + ); |
|
| 66 | + } |
|
| 67 | + |
|
| 68 | + #[NoCSRFRequired] |
|
| 69 | + #[AuthorizedAdminSetting(settings: Overview::class)] |
|
| 70 | + public function getFailedIntegrityCheckFiles(): DataDisplayResponse { |
|
| 71 | + if (!$this->checker->isCodeCheckEnforced()) { |
|
| 72 | + return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.'); |
|
| 73 | + } |
|
| 74 | + |
|
| 75 | + $completeResults = $this->checker->getResults(); |
|
| 76 | + |
|
| 77 | + if ($completeResults === null) { |
|
| 78 | + return new DataDisplayResponse('Integrity checker has not been run. Integrity information not available.'); |
|
| 79 | + } |
|
| 80 | + |
|
| 81 | + if (!empty($completeResults)) { |
|
| 82 | + $formattedTextResponse = 'Technical information |
|
| 83 | 83 | ===================== |
| 84 | 84 | The following list covers which files have failed the integrity check. Please read |
| 85 | 85 | the previous linked documentation to learn more about the errors and how to fix |
@@ -88,47 +88,47 @@ discard block |
||
| 88 | 88 | Results |
| 89 | 89 | ======= |
| 90 | 90 | '; |
| 91 | - foreach ($completeResults as $context => $contextResult) { |
|
| 92 | - $formattedTextResponse .= "- $context\n"; |
|
| 93 | - |
|
| 94 | - foreach ($contextResult as $category => $result) { |
|
| 95 | - $formattedTextResponse .= "\t- $category\n"; |
|
| 96 | - if ($category !== 'EXCEPTION') { |
|
| 97 | - foreach ($result as $key => $results) { |
|
| 98 | - $formattedTextResponse .= "\t\t- $key\n"; |
|
| 99 | - } |
|
| 100 | - } else { |
|
| 101 | - foreach ($result as $key => $results) { |
|
| 102 | - $formattedTextResponse .= "\t\t- $results\n"; |
|
| 103 | - } |
|
| 104 | - } |
|
| 105 | - } |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - $formattedTextResponse .= ' |
|
| 91 | + foreach ($completeResults as $context => $contextResult) { |
|
| 92 | + $formattedTextResponse .= "- $context\n"; |
|
| 93 | + |
|
| 94 | + foreach ($contextResult as $category => $result) { |
|
| 95 | + $formattedTextResponse .= "\t- $category\n"; |
|
| 96 | + if ($category !== 'EXCEPTION') { |
|
| 97 | + foreach ($result as $key => $results) { |
|
| 98 | + $formattedTextResponse .= "\t\t- $key\n"; |
|
| 99 | + } |
|
| 100 | + } else { |
|
| 101 | + foreach ($result as $key => $results) { |
|
| 102 | + $formattedTextResponse .= "\t\t- $results\n"; |
|
| 103 | + } |
|
| 104 | + } |
|
| 105 | + } |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + $formattedTextResponse .= ' |
|
| 109 | 109 | Raw output |
| 110 | 110 | ========== |
| 111 | 111 | '; |
| 112 | - $formattedTextResponse .= print_r($completeResults, true); |
|
| 113 | - } else { |
|
| 114 | - $formattedTextResponse = 'No errors have been found.'; |
|
| 115 | - } |
|
| 116 | - |
|
| 117 | - |
|
| 118 | - return new DataDisplayResponse( |
|
| 119 | - $formattedTextResponse, |
|
| 120 | - Http::STATUS_OK, |
|
| 121 | - [ |
|
| 122 | - 'Content-Type' => 'text/plain', |
|
| 123 | - ] |
|
| 124 | - ); |
|
| 125 | - } |
|
| 126 | - |
|
| 127 | - /** |
|
| 128 | - * @return DataResponse |
|
| 129 | - */ |
|
| 130 | - #[AuthorizedAdminSetting(settings: Overview::class)] |
|
| 131 | - public function check() { |
|
| 132 | - return new DataResponse($this->setupCheckManager->runAll()); |
|
| 133 | - } |
|
| 112 | + $formattedTextResponse .= print_r($completeResults, true); |
|
| 113 | + } else { |
|
| 114 | + $formattedTextResponse = 'No errors have been found.'; |
|
| 115 | + } |
|
| 116 | + |
|
| 117 | + |
|
| 118 | + return new DataDisplayResponse( |
|
| 119 | + $formattedTextResponse, |
|
| 120 | + Http::STATUS_OK, |
|
| 121 | + [ |
|
| 122 | + 'Content-Type' => 'text/plain', |
|
| 123 | + ] |
|
| 124 | + ); |
|
| 125 | + } |
|
| 126 | + |
|
| 127 | + /** |
|
| 128 | + * @return DataResponse |
|
| 129 | + */ |
|
| 130 | + #[AuthorizedAdminSetting(settings: Overview::class)] |
|
| 131 | + public function check() { |
|
| 132 | + return new DataResponse($this->setupCheckManager->runAll()); |
|
| 133 | + } |
|
| 134 | 134 | } |
@@ -23,119 +23,119 @@ |
||
| 23 | 23 | use Psr\Log\LoggerInterface; |
| 24 | 24 | |
| 25 | 25 | class ExpireTrash extends TimedJob { |
| 26 | - public const TOGGLE_CONFIG_KEY_NAME = 'background_job_expire_trash'; |
|
| 27 | - public const OFFSET_CONFIG_KEY_NAME = 'background_job_expire_trash_offset'; |
|
| 28 | - private const THIRTY_MINUTES = 30 * 60; |
|
| 29 | - private const USER_BATCH_SIZE = 10; |
|
| 30 | - |
|
| 31 | - public function __construct( |
|
| 32 | - private IAppConfig $appConfig, |
|
| 33 | - private IUserManager $userManager, |
|
| 34 | - private Expiration $expiration, |
|
| 35 | - private LoggerInterface $logger, |
|
| 36 | - private SetupManager $setupManager, |
|
| 37 | - private ILockingProvider $lockingProvider, |
|
| 38 | - ITimeFactory $time, |
|
| 39 | - ) { |
|
| 40 | - parent::__construct($time); |
|
| 41 | - $this->setInterval(self::THIRTY_MINUTES); |
|
| 42 | - } |
|
| 43 | - |
|
| 44 | - protected function run($argument) { |
|
| 45 | - $backgroundJob = $this->appConfig->getValueBool(Application::APP_ID, self::TOGGLE_CONFIG_KEY_NAME, true); |
|
| 46 | - if (!$backgroundJob) { |
|
| 47 | - return; |
|
| 48 | - } |
|
| 49 | - |
|
| 50 | - $maxAge = $this->expiration->getMaxAgeAsTimestamp(); |
|
| 51 | - if (!$maxAge) { |
|
| 52 | - return; |
|
| 53 | - } |
|
| 54 | - |
|
| 55 | - $startTime = time(); |
|
| 56 | - |
|
| 57 | - // Process users in batches of 10, but don't run for more than 30 minutes |
|
| 58 | - while (time() < $startTime + self::THIRTY_MINUTES) { |
|
| 59 | - $offset = $this->getNextOffset(); |
|
| 60 | - $users = $this->userManager->getSeenUsers($offset, self::USER_BATCH_SIZE); |
|
| 61 | - $count = 0; |
|
| 62 | - |
|
| 63 | - foreach ($users as $user) { |
|
| 64 | - $uid = $user->getUID(); |
|
| 65 | - $count++; |
|
| 66 | - |
|
| 67 | - try { |
|
| 68 | - if ($this->setupFS($user)) { |
|
| 69 | - $dirContent = Helper::getTrashFiles('/', $uid, 'mtime'); |
|
| 70 | - Trashbin::deleteExpiredFiles($dirContent, $uid); |
|
| 71 | - } |
|
| 72 | - } catch (\Throwable $e) { |
|
| 73 | - $this->logger->error('Error while expiring trashbin for user ' . $uid, ['exception' => $e]); |
|
| 74 | - } finally { |
|
| 75 | - $this->setupManager->tearDown(); |
|
| 76 | - } |
|
| 77 | - } |
|
| 78 | - |
|
| 79 | - // If the last batch was not full it means that we reached the end of the user list. |
|
| 80 | - if ($count < self::USER_BATCH_SIZE) { |
|
| 81 | - $this->resetOffset(); |
|
| 82 | - break; |
|
| 83 | - } |
|
| 84 | - } |
|
| 85 | - } |
|
| 86 | - |
|
| 87 | - /** |
|
| 88 | - * Act on behalf on trash item owner |
|
| 89 | - */ |
|
| 90 | - protected function setupFS(IUser $user): bool { |
|
| 91 | - $this->setupManager->setupForUser($user); |
|
| 92 | - |
|
| 93 | - // Check if this user has a trashbin directory |
|
| 94 | - $view = new View('/' . $user->getUID()); |
|
| 95 | - if (!$view->is_dir('/files_trashbin/files')) { |
|
| 96 | - return false; |
|
| 97 | - } |
|
| 98 | - |
|
| 99 | - return true; |
|
| 100 | - } |
|
| 101 | - |
|
| 102 | - private function getNextOffset(): int { |
|
| 103 | - return $this->runMutexOperation(function () { |
|
| 104 | - $this->appConfig->clearCache(); |
|
| 105 | - |
|
| 106 | - $offset = $this->appConfig->getValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, 0); |
|
| 107 | - $this->appConfig->setValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, $offset + self::USER_BATCH_SIZE); |
|
| 108 | - |
|
| 109 | - return $offset; |
|
| 110 | - }); |
|
| 111 | - |
|
| 112 | - } |
|
| 113 | - |
|
| 114 | - private function resetOffset() { |
|
| 115 | - $this->runMutexOperation(function (): void { |
|
| 116 | - $this->appConfig->setValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, 0); |
|
| 117 | - }); |
|
| 118 | - } |
|
| 119 | - |
|
| 120 | - private function runMutexOperation($operation): mixed { |
|
| 121 | - $acquired = false; |
|
| 122 | - |
|
| 123 | - while ($acquired === false) { |
|
| 124 | - try { |
|
| 125 | - $this->lockingProvider->acquireLock(self::OFFSET_CONFIG_KEY_NAME, ILockingProvider::LOCK_EXCLUSIVE, 'Expire trashbin background job offset'); |
|
| 126 | - $acquired = true; |
|
| 127 | - } catch (LockedException $e) { |
|
| 128 | - // wait a bit and try again |
|
| 129 | - usleep(100000); |
|
| 130 | - } |
|
| 131 | - } |
|
| 132 | - |
|
| 133 | - try { |
|
| 134 | - $result = $operation(); |
|
| 135 | - } finally { |
|
| 136 | - $this->lockingProvider->releaseLock(self::OFFSET_CONFIG_KEY_NAME, ILockingProvider::LOCK_EXCLUSIVE); |
|
| 137 | - } |
|
| 138 | - |
|
| 139 | - return $result; |
|
| 140 | - } |
|
| 26 | + public const TOGGLE_CONFIG_KEY_NAME = 'background_job_expire_trash'; |
|
| 27 | + public const OFFSET_CONFIG_KEY_NAME = 'background_job_expire_trash_offset'; |
|
| 28 | + private const THIRTY_MINUTES = 30 * 60; |
|
| 29 | + private const USER_BATCH_SIZE = 10; |
|
| 30 | + |
|
| 31 | + public function __construct( |
|
| 32 | + private IAppConfig $appConfig, |
|
| 33 | + private IUserManager $userManager, |
|
| 34 | + private Expiration $expiration, |
|
| 35 | + private LoggerInterface $logger, |
|
| 36 | + private SetupManager $setupManager, |
|
| 37 | + private ILockingProvider $lockingProvider, |
|
| 38 | + ITimeFactory $time, |
|
| 39 | + ) { |
|
| 40 | + parent::__construct($time); |
|
| 41 | + $this->setInterval(self::THIRTY_MINUTES); |
|
| 42 | + } |
|
| 43 | + |
|
| 44 | + protected function run($argument) { |
|
| 45 | + $backgroundJob = $this->appConfig->getValueBool(Application::APP_ID, self::TOGGLE_CONFIG_KEY_NAME, true); |
|
| 46 | + if (!$backgroundJob) { |
|
| 47 | + return; |
|
| 48 | + } |
|
| 49 | + |
|
| 50 | + $maxAge = $this->expiration->getMaxAgeAsTimestamp(); |
|
| 51 | + if (!$maxAge) { |
|
| 52 | + return; |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + $startTime = time(); |
|
| 56 | + |
|
| 57 | + // Process users in batches of 10, but don't run for more than 30 minutes |
|
| 58 | + while (time() < $startTime + self::THIRTY_MINUTES) { |
|
| 59 | + $offset = $this->getNextOffset(); |
|
| 60 | + $users = $this->userManager->getSeenUsers($offset, self::USER_BATCH_SIZE); |
|
| 61 | + $count = 0; |
|
| 62 | + |
|
| 63 | + foreach ($users as $user) { |
|
| 64 | + $uid = $user->getUID(); |
|
| 65 | + $count++; |
|
| 66 | + |
|
| 67 | + try { |
|
| 68 | + if ($this->setupFS($user)) { |
|
| 69 | + $dirContent = Helper::getTrashFiles('/', $uid, 'mtime'); |
|
| 70 | + Trashbin::deleteExpiredFiles($dirContent, $uid); |
|
| 71 | + } |
|
| 72 | + } catch (\Throwable $e) { |
|
| 73 | + $this->logger->error('Error while expiring trashbin for user ' . $uid, ['exception' => $e]); |
|
| 74 | + } finally { |
|
| 75 | + $this->setupManager->tearDown(); |
|
| 76 | + } |
|
| 77 | + } |
|
| 78 | + |
|
| 79 | + // If the last batch was not full it means that we reached the end of the user list. |
|
| 80 | + if ($count < self::USER_BATCH_SIZE) { |
|
| 81 | + $this->resetOffset(); |
|
| 82 | + break; |
|
| 83 | + } |
|
| 84 | + } |
|
| 85 | + } |
|
| 86 | + |
|
| 87 | + /** |
|
| 88 | + * Act on behalf on trash item owner |
|
| 89 | + */ |
|
| 90 | + protected function setupFS(IUser $user): bool { |
|
| 91 | + $this->setupManager->setupForUser($user); |
|
| 92 | + |
|
| 93 | + // Check if this user has a trashbin directory |
|
| 94 | + $view = new View('/' . $user->getUID()); |
|
| 95 | + if (!$view->is_dir('/files_trashbin/files')) { |
|
| 96 | + return false; |
|
| 97 | + } |
|
| 98 | + |
|
| 99 | + return true; |
|
| 100 | + } |
|
| 101 | + |
|
| 102 | + private function getNextOffset(): int { |
|
| 103 | + return $this->runMutexOperation(function () { |
|
| 104 | + $this->appConfig->clearCache(); |
|
| 105 | + |
|
| 106 | + $offset = $this->appConfig->getValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, 0); |
|
| 107 | + $this->appConfig->setValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, $offset + self::USER_BATCH_SIZE); |
|
| 108 | + |
|
| 109 | + return $offset; |
|
| 110 | + }); |
|
| 111 | + |
|
| 112 | + } |
|
| 113 | + |
|
| 114 | + private function resetOffset() { |
|
| 115 | + $this->runMutexOperation(function (): void { |
|
| 116 | + $this->appConfig->setValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, 0); |
|
| 117 | + }); |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + private function runMutexOperation($operation): mixed { |
|
| 121 | + $acquired = false; |
|
| 122 | + |
|
| 123 | + while ($acquired === false) { |
|
| 124 | + try { |
|
| 125 | + $this->lockingProvider->acquireLock(self::OFFSET_CONFIG_KEY_NAME, ILockingProvider::LOCK_EXCLUSIVE, 'Expire trashbin background job offset'); |
|
| 126 | + $acquired = true; |
|
| 127 | + } catch (LockedException $e) { |
|
| 128 | + // wait a bit and try again |
|
| 129 | + usleep(100000); |
|
| 130 | + } |
|
| 131 | + } |
|
| 132 | + |
|
| 133 | + try { |
|
| 134 | + $result = $operation(); |
|
| 135 | + } finally { |
|
| 136 | + $this->lockingProvider->releaseLock(self::OFFSET_CONFIG_KEY_NAME, ILockingProvider::LOCK_EXCLUSIVE); |
|
| 137 | + } |
|
| 138 | + |
|
| 139 | + return $result; |
|
| 140 | + } |
|
| 141 | 141 | } |
@@ -70,7 +70,7 @@ discard block |
||
| 70 | 70 | Trashbin::deleteExpiredFiles($dirContent, $uid); |
| 71 | 71 | } |
| 72 | 72 | } catch (\Throwable $e) { |
| 73 | - $this->logger->error('Error while expiring trashbin for user ' . $uid, ['exception' => $e]); |
|
| 73 | + $this->logger->error('Error while expiring trashbin for user '.$uid, ['exception' => $e]); |
|
| 74 | 74 | } finally { |
| 75 | 75 | $this->setupManager->tearDown(); |
| 76 | 76 | } |
@@ -91,7 +91,7 @@ discard block |
||
| 91 | 91 | $this->setupManager->setupForUser($user); |
| 92 | 92 | |
| 93 | 93 | // Check if this user has a trashbin directory |
| 94 | - $view = new View('/' . $user->getUID()); |
|
| 94 | + $view = new View('/'.$user->getUID()); |
|
| 95 | 95 | if (!$view->is_dir('/files_trashbin/files')) { |
| 96 | 96 | return false; |
| 97 | 97 | } |
@@ -100,7 +100,7 @@ discard block |
||
| 100 | 100 | } |
| 101 | 101 | |
| 102 | 102 | private function getNextOffset(): int { |
| 103 | - return $this->runMutexOperation(function () { |
|
| 103 | + return $this->runMutexOperation(function() { |
|
| 104 | 104 | $this->appConfig->clearCache(); |
| 105 | 105 | |
| 106 | 106 | $offset = $this->appConfig->getValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, 0); |
@@ -112,7 +112,7 @@ discard block |
||
| 112 | 112 | } |
| 113 | 113 | |
| 114 | 114 | private function resetOffset() { |
| 115 | - $this->runMutexOperation(function (): void { |
|
| 115 | + $this->runMutexOperation(function(): void { |
|
| 116 | 116 | $this->appConfig->setValueInt(Application::APP_ID, self::OFFSET_CONFIG_KEY_NAME, 0); |
| 117 | 117 | }); |
| 118 | 118 | } |
@@ -29,126 +29,126 @@ |
||
| 29 | 29 | * @package OCA\Files_Trashbin\Tests\Command |
| 30 | 30 | */ |
| 31 | 31 | class ExpireTrashTest extends TestCase { |
| 32 | - private Expiration $expiration; |
|
| 33 | - private Node $userFolder; |
|
| 34 | - private IConfig $config; |
|
| 35 | - private IUserManager $userManager; |
|
| 36 | - private IUser $user; |
|
| 37 | - private ITimeFactory $timeFactory; |
|
| 32 | + private Expiration $expiration; |
|
| 33 | + private Node $userFolder; |
|
| 34 | + private IConfig $config; |
|
| 35 | + private IUserManager $userManager; |
|
| 36 | + private IUser $user; |
|
| 37 | + private ITimeFactory $timeFactory; |
|
| 38 | 38 | |
| 39 | - |
|
| 40 | - protected function setUp(): void { |
|
| 41 | - parent::setUp(); |
|
| 42 | - |
|
| 43 | - $this->config = Server::get(IConfig::class); |
|
| 44 | - $this->timeFactory = $this->createMock(ITimeFactory::class); |
|
| 45 | - $this->expiration = Server::get(Expiration::class); |
|
| 46 | - $this->invokePrivate($this->expiration, 'timeFactory', [$this->timeFactory]); |
|
| 47 | - |
|
| 48 | - $userId = self::getUniqueID('user'); |
|
| 49 | - $this->userManager = Server::get(IUserManager::class); |
|
| 50 | - $this->user = $this->userManager->createUser($userId, $userId); |
|
| 51 | - |
|
| 52 | - $this->loginAsUser($userId); |
|
| 53 | - $this->userFolder = Server::get(IRootFolder::class)->getUserFolder($userId); |
|
| 54 | - } |
|
| 55 | - |
|
| 56 | - protected function tearDown(): void { |
|
| 57 | - $this->logout(); |
|
| 58 | - |
|
| 59 | - if (isset($this->user)) { |
|
| 60 | - $this->user->delete(); |
|
| 61 | - } |
|
| 62 | - |
|
| 63 | - $this->invokePrivate($this->expiration, 'timeFactory', [Server::get(ITimeFactory::class)]); |
|
| 64 | - parent::tearDown(); |
|
| 65 | - } |
|
| 66 | - |
|
| 67 | - #[\PHPUnit\Framework\Attributes\DataProvider('retentionObligationProvider')] |
|
| 68 | - public function testRetentionObligation(string $obligation, string $quota, int $elapsed, int $fileSize, bool $shouldExpire): void { |
|
| 69 | - $this->config->setSystemValues(['trashbin_retention_obligation' => $obligation]); |
|
| 70 | - $this->expiration->setRetentionObligation($obligation); |
|
| 71 | - |
|
| 72 | - $this->user->setQuota($quota); |
|
| 73 | - |
|
| 74 | - $bytes = 'ABCDEFGHIKLMNOPQRSTUVWXYZ'; |
|
| 75 | - |
|
| 76 | - $file = 'foo.txt'; |
|
| 77 | - $this->userFolder->newFile($file, substr($bytes, 0, $fileSize)); |
|
| 78 | - |
|
| 79 | - $filemtime = $this->userFolder->get($file)->getMTime(); |
|
| 80 | - $this->timeFactory->expects($this->any()) |
|
| 81 | - ->method('getTime') |
|
| 82 | - ->willReturn($filemtime + $elapsed); |
|
| 83 | - $this->userFolder->get($file)->delete(); |
|
| 84 | - $this->userFolder->getStorage() |
|
| 85 | - ->getCache() |
|
| 86 | - ->put('files_trashbin', ['size' => $fileSize, 'unencrypted_size' => $fileSize]); |
|
| 87 | - |
|
| 88 | - $userId = $this->user->getUID(); |
|
| 89 | - $trashFiles = Helper::getTrashFiles('/', $userId); |
|
| 90 | - $this->assertEquals(1, count($trashFiles)); |
|
| 91 | - |
|
| 92 | - $outputInterface = $this->createMock(OutputInterface::class); |
|
| 93 | - $inputInterface = $this->createMock(InputInterface::class); |
|
| 94 | - $inputInterface->expects($this->any()) |
|
| 95 | - ->method('getArgument') |
|
| 96 | - ->with('user_id') |
|
| 97 | - ->willReturn([$userId]); |
|
| 98 | - |
|
| 99 | - $command = new ExpireTrash( |
|
| 100 | - Server::get(LoggerInterface::class), |
|
| 101 | - Server::get(IUserManager::class), |
|
| 102 | - $this->expiration |
|
| 103 | - ); |
|
| 104 | - |
|
| 105 | - $this->invokePrivate($command, 'execute', [$inputInterface, $outputInterface]); |
|
| 106 | - |
|
| 107 | - $trashFiles = Helper::getTrashFiles('/', $userId); |
|
| 108 | - $this->assertEquals($shouldExpire ? 0 : 1, count($trashFiles)); |
|
| 109 | - } |
|
| 110 | - |
|
| 111 | - public static function retentionObligationProvider(): array { |
|
| 112 | - $hour = 3600; // 60 * 60 |
|
| 113 | - |
|
| 114 | - $oneDay = 24 * $hour; |
|
| 115 | - $fiveDays = 24 * 5 * $hour; |
|
| 116 | - $tenDays = 24 * 10 * $hour; |
|
| 117 | - $elevenDays = 24 * 11 * $hour; |
|
| 118 | - |
|
| 119 | - return [ |
|
| 120 | - ['disabled', '20 B', 0, 1, false], |
|
| 121 | - |
|
| 122 | - ['auto', '20 B', 0, 5, false], |
|
| 123 | - ['auto', '20 B', 0, 21, true], |
|
| 124 | - |
|
| 125 | - ['0, auto', '20 B', 0, 21, true], |
|
| 126 | - ['0, auto', '20 B', $oneDay, 5, false], |
|
| 127 | - ['0, auto', '20 B', $oneDay, 19, true], |
|
| 128 | - ['0, auto', '20 B', 0, 19, true], |
|
| 129 | - |
|
| 130 | - ['auto, 0', '20 B', $oneDay, 19, true], |
|
| 131 | - ['auto, 0', '20 B', $oneDay, 21, true], |
|
| 132 | - ['auto, 0', '20 B', 0, 5, false], |
|
| 133 | - ['auto, 0', '20 B', 0, 19, true], |
|
| 134 | - |
|
| 135 | - ['1, auto', '20 B', 0, 5, false], |
|
| 136 | - ['1, auto', '20 B', $fiveDays, 5, false], |
|
| 137 | - ['1, auto', '20 B', $fiveDays, 21, true], |
|
| 138 | - |
|
| 139 | - ['auto, 1', '20 B', 0, 21, true], |
|
| 140 | - ['auto, 1', '20 B', 0, 5, false], |
|
| 141 | - ['auto, 1', '20 B', $fiveDays, 5, true], |
|
| 142 | - ['auto, 1', '20 B', $oneDay, 5, false], |
|
| 143 | - |
|
| 144 | - ['2, 10', '20 B', $fiveDays, 5, false], |
|
| 145 | - ['2, 10', '20 B', $fiveDays, 20, true], |
|
| 146 | - ['2, 10', '20 B', $elevenDays, 5, true], |
|
| 147 | - |
|
| 148 | - ['10, 2', '20 B', $fiveDays, 5, false], |
|
| 149 | - ['10, 2', '20 B', $fiveDays, 21, false], |
|
| 150 | - ['10, 2', '20 B', $tenDays, 5, false], |
|
| 151 | - ['10, 2', '20 B', $elevenDays, 5, true] |
|
| 152 | - ]; |
|
| 153 | - } |
|
| 39 | + |
|
| 40 | + protected function setUp(): void { |
|
| 41 | + parent::setUp(); |
|
| 42 | + |
|
| 43 | + $this->config = Server::get(IConfig::class); |
|
| 44 | + $this->timeFactory = $this->createMock(ITimeFactory::class); |
|
| 45 | + $this->expiration = Server::get(Expiration::class); |
|
| 46 | + $this->invokePrivate($this->expiration, 'timeFactory', [$this->timeFactory]); |
|
| 47 | + |
|
| 48 | + $userId = self::getUniqueID('user'); |
|
| 49 | + $this->userManager = Server::get(IUserManager::class); |
|
| 50 | + $this->user = $this->userManager->createUser($userId, $userId); |
|
| 51 | + |
|
| 52 | + $this->loginAsUser($userId); |
|
| 53 | + $this->userFolder = Server::get(IRootFolder::class)->getUserFolder($userId); |
|
| 54 | + } |
|
| 55 | + |
|
| 56 | + protected function tearDown(): void { |
|
| 57 | + $this->logout(); |
|
| 58 | + |
|
| 59 | + if (isset($this->user)) { |
|
| 60 | + $this->user->delete(); |
|
| 61 | + } |
|
| 62 | + |
|
| 63 | + $this->invokePrivate($this->expiration, 'timeFactory', [Server::get(ITimeFactory::class)]); |
|
| 64 | + parent::tearDown(); |
|
| 65 | + } |
|
| 66 | + |
|
| 67 | + #[\PHPUnit\Framework\Attributes\DataProvider('retentionObligationProvider')] |
|
| 68 | + public function testRetentionObligation(string $obligation, string $quota, int $elapsed, int $fileSize, bool $shouldExpire): void { |
|
| 69 | + $this->config->setSystemValues(['trashbin_retention_obligation' => $obligation]); |
|
| 70 | + $this->expiration->setRetentionObligation($obligation); |
|
| 71 | + |
|
| 72 | + $this->user->setQuota($quota); |
|
| 73 | + |
|
| 74 | + $bytes = 'ABCDEFGHIKLMNOPQRSTUVWXYZ'; |
|
| 75 | + |
|
| 76 | + $file = 'foo.txt'; |
|
| 77 | + $this->userFolder->newFile($file, substr($bytes, 0, $fileSize)); |
|
| 78 | + |
|
| 79 | + $filemtime = $this->userFolder->get($file)->getMTime(); |
|
| 80 | + $this->timeFactory->expects($this->any()) |
|
| 81 | + ->method('getTime') |
|
| 82 | + ->willReturn($filemtime + $elapsed); |
|
| 83 | + $this->userFolder->get($file)->delete(); |
|
| 84 | + $this->userFolder->getStorage() |
|
| 85 | + ->getCache() |
|
| 86 | + ->put('files_trashbin', ['size' => $fileSize, 'unencrypted_size' => $fileSize]); |
|
| 87 | + |
|
| 88 | + $userId = $this->user->getUID(); |
|
| 89 | + $trashFiles = Helper::getTrashFiles('/', $userId); |
|
| 90 | + $this->assertEquals(1, count($trashFiles)); |
|
| 91 | + |
|
| 92 | + $outputInterface = $this->createMock(OutputInterface::class); |
|
| 93 | + $inputInterface = $this->createMock(InputInterface::class); |
|
| 94 | + $inputInterface->expects($this->any()) |
|
| 95 | + ->method('getArgument') |
|
| 96 | + ->with('user_id') |
|
| 97 | + ->willReturn([$userId]); |
|
| 98 | + |
|
| 99 | + $command = new ExpireTrash( |
|
| 100 | + Server::get(LoggerInterface::class), |
|
| 101 | + Server::get(IUserManager::class), |
|
| 102 | + $this->expiration |
|
| 103 | + ); |
|
| 104 | + |
|
| 105 | + $this->invokePrivate($command, 'execute', [$inputInterface, $outputInterface]); |
|
| 106 | + |
|
| 107 | + $trashFiles = Helper::getTrashFiles('/', $userId); |
|
| 108 | + $this->assertEquals($shouldExpire ? 0 : 1, count($trashFiles)); |
|
| 109 | + } |
|
| 110 | + |
|
| 111 | + public static function retentionObligationProvider(): array { |
|
| 112 | + $hour = 3600; // 60 * 60 |
|
| 113 | + |
|
| 114 | + $oneDay = 24 * $hour; |
|
| 115 | + $fiveDays = 24 * 5 * $hour; |
|
| 116 | + $tenDays = 24 * 10 * $hour; |
|
| 117 | + $elevenDays = 24 * 11 * $hour; |
|
| 118 | + |
|
| 119 | + return [ |
|
| 120 | + ['disabled', '20 B', 0, 1, false], |
|
| 121 | + |
|
| 122 | + ['auto', '20 B', 0, 5, false], |
|
| 123 | + ['auto', '20 B', 0, 21, true], |
|
| 124 | + |
|
| 125 | + ['0, auto', '20 B', 0, 21, true], |
|
| 126 | + ['0, auto', '20 B', $oneDay, 5, false], |
|
| 127 | + ['0, auto', '20 B', $oneDay, 19, true], |
|
| 128 | + ['0, auto', '20 B', 0, 19, true], |
|
| 129 | + |
|
| 130 | + ['auto, 0', '20 B', $oneDay, 19, true], |
|
| 131 | + ['auto, 0', '20 B', $oneDay, 21, true], |
|
| 132 | + ['auto, 0', '20 B', 0, 5, false], |
|
| 133 | + ['auto, 0', '20 B', 0, 19, true], |
|
| 134 | + |
|
| 135 | + ['1, auto', '20 B', 0, 5, false], |
|
| 136 | + ['1, auto', '20 B', $fiveDays, 5, false], |
|
| 137 | + ['1, auto', '20 B', $fiveDays, 21, true], |
|
| 138 | + |
|
| 139 | + ['auto, 1', '20 B', 0, 21, true], |
|
| 140 | + ['auto, 1', '20 B', 0, 5, false], |
|
| 141 | + ['auto, 1', '20 B', $fiveDays, 5, true], |
|
| 142 | + ['auto, 1', '20 B', $oneDay, 5, false], |
|
| 143 | + |
|
| 144 | + ['2, 10', '20 B', $fiveDays, 5, false], |
|
| 145 | + ['2, 10', '20 B', $fiveDays, 20, true], |
|
| 146 | + ['2, 10', '20 B', $elevenDays, 5, true], |
|
| 147 | + |
|
| 148 | + ['10, 2', '20 B', $fiveDays, 5, false], |
|
| 149 | + ['10, 2', '20 B', $fiveDays, 21, false], |
|
| 150 | + ['10, 2', '20 B', $tenDays, 5, false], |
|
| 151 | + ['10, 2', '20 B', $elevenDays, 5, true] |
|
| 152 | + ]; |
|
| 153 | + } |
|
| 154 | 154 | } |
@@ -14,96 +14,96 @@ |
||
| 14 | 14 | * @deprecated 20.0.0 |
| 15 | 15 | */ |
| 16 | 16 | class Result extends BaseResult { |
| 17 | - /** |
|
| 18 | - * @deprecated 20.0.0 |
|
| 19 | - */ |
|
| 20 | - public $type = 'comment'; |
|
| 21 | - /** |
|
| 22 | - * @deprecated 20.0.0 |
|
| 23 | - */ |
|
| 24 | - public $comment; |
|
| 25 | - /** |
|
| 26 | - * @deprecated 20.0.0 |
|
| 27 | - */ |
|
| 28 | - public $authorId; |
|
| 29 | - /** |
|
| 30 | - * @deprecated 20.0.0 |
|
| 31 | - */ |
|
| 32 | - public $path; |
|
| 33 | - /** |
|
| 34 | - * @deprecated 20.0.0 |
|
| 35 | - */ |
|
| 36 | - public $fileName; |
|
| 17 | + /** |
|
| 18 | + * @deprecated 20.0.0 |
|
| 19 | + */ |
|
| 20 | + public $type = 'comment'; |
|
| 21 | + /** |
|
| 22 | + * @deprecated 20.0.0 |
|
| 23 | + */ |
|
| 24 | + public $comment; |
|
| 25 | + /** |
|
| 26 | + * @deprecated 20.0.0 |
|
| 27 | + */ |
|
| 28 | + public $authorId; |
|
| 29 | + /** |
|
| 30 | + * @deprecated 20.0.0 |
|
| 31 | + */ |
|
| 32 | + public $path; |
|
| 33 | + /** |
|
| 34 | + * @deprecated 20.0.0 |
|
| 35 | + */ |
|
| 36 | + public $fileName; |
|
| 37 | 37 | |
| 38 | - /** |
|
| 39 | - * @throws NotFoundException |
|
| 40 | - * @deprecated 20.0.0 |
|
| 41 | - */ |
|
| 42 | - public function __construct( |
|
| 43 | - string $search, |
|
| 44 | - IComment $comment, |
|
| 45 | - /** |
|
| 46 | - * @deprecated 20.0.0 |
|
| 47 | - */ |
|
| 48 | - public string $authorName, |
|
| 49 | - string $path, |
|
| 50 | - /** |
|
| 51 | - * @deprecated 20.0.0 |
|
| 52 | - */ |
|
| 53 | - public int $fileId, |
|
| 54 | - ) { |
|
| 55 | - parent::__construct( |
|
| 56 | - $comment->getId(), |
|
| 57 | - $comment->getMessage() |
|
| 58 | - /* @todo , [link to file] */ |
|
| 59 | - ); |
|
| 38 | + /** |
|
| 39 | + * @throws NotFoundException |
|
| 40 | + * @deprecated 20.0.0 |
|
| 41 | + */ |
|
| 42 | + public function __construct( |
|
| 43 | + string $search, |
|
| 44 | + IComment $comment, |
|
| 45 | + /** |
|
| 46 | + * @deprecated 20.0.0 |
|
| 47 | + */ |
|
| 48 | + public string $authorName, |
|
| 49 | + string $path, |
|
| 50 | + /** |
|
| 51 | + * @deprecated 20.0.0 |
|
| 52 | + */ |
|
| 53 | + public int $fileId, |
|
| 54 | + ) { |
|
| 55 | + parent::__construct( |
|
| 56 | + $comment->getId(), |
|
| 57 | + $comment->getMessage() |
|
| 58 | + /* @todo , [link to file] */ |
|
| 59 | + ); |
|
| 60 | 60 | |
| 61 | - $this->comment = $this->getRelevantMessagePart($comment->getMessage(), $search); |
|
| 62 | - $this->authorId = $comment->getActorId(); |
|
| 63 | - $this->fileName = basename($path); |
|
| 64 | - $this->path = $this->getVisiblePath($path); |
|
| 65 | - } |
|
| 61 | + $this->comment = $this->getRelevantMessagePart($comment->getMessage(), $search); |
|
| 62 | + $this->authorId = $comment->getActorId(); |
|
| 63 | + $this->fileName = basename($path); |
|
| 64 | + $this->path = $this->getVisiblePath($path); |
|
| 65 | + } |
|
| 66 | 66 | |
| 67 | - /** |
|
| 68 | - * @throws NotFoundException |
|
| 69 | - */ |
|
| 70 | - protected function getVisiblePath(string $path): string { |
|
| 71 | - $segments = explode('/', trim($path, '/'), 3); |
|
| 67 | + /** |
|
| 68 | + * @throws NotFoundException |
|
| 69 | + */ |
|
| 70 | + protected function getVisiblePath(string $path): string { |
|
| 71 | + $segments = explode('/', trim($path, '/'), 3); |
|
| 72 | 72 | |
| 73 | - if (!isset($segments[2])) { |
|
| 74 | - throw new NotFoundException('Path not inside visible section'); |
|
| 75 | - } |
|
| 73 | + if (!isset($segments[2])) { |
|
| 74 | + throw new NotFoundException('Path not inside visible section'); |
|
| 75 | + } |
|
| 76 | 76 | |
| 77 | - return $segments[2]; |
|
| 78 | - } |
|
| 77 | + return $segments[2]; |
|
| 78 | + } |
|
| 79 | 79 | |
| 80 | - /** |
|
| 81 | - * @throws NotFoundException |
|
| 82 | - */ |
|
| 83 | - protected function getRelevantMessagePart(string $message, string $search): string { |
|
| 84 | - $start = mb_stripos($message, $search); |
|
| 85 | - if ($start === false) { |
|
| 86 | - throw new NotFoundException('Comment section not found'); |
|
| 87 | - } |
|
| 80 | + /** |
|
| 81 | + * @throws NotFoundException |
|
| 82 | + */ |
|
| 83 | + protected function getRelevantMessagePart(string $message, string $search): string { |
|
| 84 | + $start = mb_stripos($message, $search); |
|
| 85 | + if ($start === false) { |
|
| 86 | + throw new NotFoundException('Comment section not found'); |
|
| 87 | + } |
|
| 88 | 88 | |
| 89 | - $end = $start + mb_strlen($search); |
|
| 89 | + $end = $start + mb_strlen($search); |
|
| 90 | 90 | |
| 91 | - if ($start <= 25) { |
|
| 92 | - $start = 0; |
|
| 93 | - $prefix = ''; |
|
| 94 | - } else { |
|
| 95 | - $start -= 25; |
|
| 96 | - $prefix = '…'; |
|
| 97 | - } |
|
| 91 | + if ($start <= 25) { |
|
| 92 | + $start = 0; |
|
| 93 | + $prefix = ''; |
|
| 94 | + } else { |
|
| 95 | + $start -= 25; |
|
| 96 | + $prefix = '…'; |
|
| 97 | + } |
|
| 98 | 98 | |
| 99 | - if ((mb_strlen($message) - $end) <= 25) { |
|
| 100 | - $end = mb_strlen($message); |
|
| 101 | - $suffix = ''; |
|
| 102 | - } else { |
|
| 103 | - $end += 25; |
|
| 104 | - $suffix = '…'; |
|
| 105 | - } |
|
| 99 | + if ((mb_strlen($message) - $end) <= 25) { |
|
| 100 | + $end = mb_strlen($message); |
|
| 101 | + $suffix = ''; |
|
| 102 | + } else { |
|
| 103 | + $end += 25; |
|
| 104 | + $suffix = '…'; |
|
| 105 | + } |
|
| 106 | 106 | |
| 107 | - return $prefix . mb_substr($message, $start, $end - $start) . $suffix; |
|
| 108 | - } |
|
| 107 | + return $prefix . mb_substr($message, $start, $end - $start) . $suffix; |
|
| 108 | + } |
|
| 109 | 109 | } |
@@ -21,105 +21,105 @@ |
||
| 21 | 21 | use Psr\Log\LoggerInterface; |
| 22 | 22 | |
| 23 | 23 | class SettingsController extends OCSController { |
| 24 | - public function __construct( |
|
| 25 | - string $appName, |
|
| 26 | - IRequest $request, |
|
| 27 | - private IL10N $l, |
|
| 28 | - private TrustedServers $trustedServers, |
|
| 29 | - private LoggerInterface $logger, |
|
| 30 | - ) { |
|
| 31 | - parent::__construct($appName, $request); |
|
| 32 | - } |
|
| 24 | + public function __construct( |
|
| 25 | + string $appName, |
|
| 26 | + IRequest $request, |
|
| 27 | + private IL10N $l, |
|
| 28 | + private TrustedServers $trustedServers, |
|
| 29 | + private LoggerInterface $logger, |
|
| 30 | + ) { |
|
| 31 | + parent::__construct($appName, $request); |
|
| 32 | + } |
|
| 33 | 33 | |
| 34 | 34 | |
| 35 | - /** |
|
| 36 | - * Add server to the list of trusted Nextcloud servers |
|
| 37 | - * |
|
| 38 | - * @param string $url The URL of the server to add |
|
| 39 | - * @return DataResponse<Http::STATUS_OK, array{id: int, message: string, url: string}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_CONFLICT, array{message: string}, array{}> |
|
| 40 | - * |
|
| 41 | - * 200: Server added successfully |
|
| 42 | - * 404: Server not found at the given URL |
|
| 43 | - * 409: Server is already in the list of trusted servers |
|
| 44 | - */ |
|
| 45 | - #[AuthorizedAdminSetting(settings: Admin::class)] |
|
| 46 | - #[ApiRoute(verb: 'POST', url: '/trusted-servers')] |
|
| 47 | - public function addServer(string $url): DataResponse { |
|
| 48 | - $this->checkServer(trim($url)); |
|
| 35 | + /** |
|
| 36 | + * Add server to the list of trusted Nextcloud servers |
|
| 37 | + * |
|
| 38 | + * @param string $url The URL of the server to add |
|
| 39 | + * @return DataResponse<Http::STATUS_OK, array{id: int, message: string, url: string}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_CONFLICT, array{message: string}, array{}> |
|
| 40 | + * |
|
| 41 | + * 200: Server added successfully |
|
| 42 | + * 404: Server not found at the given URL |
|
| 43 | + * 409: Server is already in the list of trusted servers |
|
| 44 | + */ |
|
| 45 | + #[AuthorizedAdminSetting(settings: Admin::class)] |
|
| 46 | + #[ApiRoute(verb: 'POST', url: '/trusted-servers')] |
|
| 47 | + public function addServer(string $url): DataResponse { |
|
| 48 | + $this->checkServer(trim($url)); |
|
| 49 | 49 | |
| 50 | - // Add the server to the list of trusted servers, all is well |
|
| 51 | - $id = $this->trustedServers->addServer(trim($url)); |
|
| 52 | - return new DataResponse([ |
|
| 53 | - 'url' => $url, |
|
| 54 | - 'id' => $id, |
|
| 55 | - 'message' => $this->l->t('Added to the list of trusted servers') |
|
| 56 | - ]); |
|
| 57 | - } |
|
| 50 | + // Add the server to the list of trusted servers, all is well |
|
| 51 | + $id = $this->trustedServers->addServer(trim($url)); |
|
| 52 | + return new DataResponse([ |
|
| 53 | + 'url' => $url, |
|
| 54 | + 'id' => $id, |
|
| 55 | + 'message' => $this->l->t('Added to the list of trusted servers') |
|
| 56 | + ]); |
|
| 57 | + } |
|
| 58 | 58 | |
| 59 | - /** |
|
| 60 | - * Add server to the list of trusted Nextcloud servers |
|
| 61 | - * |
|
| 62 | - * @param int $id The ID of the trusted server to remove |
|
| 63 | - * @return DataResponse<Http::STATUS_OK, array{id: int}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{message: string}, array{}> |
|
| 64 | - * |
|
| 65 | - * 200: Server removed successfully |
|
| 66 | - * 404: Server not found at the given ID |
|
| 67 | - */ |
|
| 68 | - #[AuthorizedAdminSetting(settings: Admin::class)] |
|
| 69 | - #[ApiRoute(verb: 'DELETE', url: '/trusted-servers/{id}', requirements: ['id' => '\d+'])] |
|
| 70 | - public function removeServer(int $id): DataResponse { |
|
| 71 | - try { |
|
| 72 | - $this->trustedServers->getServer($id); |
|
| 73 | - } catch (\Exception $e) { |
|
| 74 | - throw new OCSNotFoundException($this->l->t('No server found with ID: %s', [$id])); |
|
| 75 | - } |
|
| 59 | + /** |
|
| 60 | + * Add server to the list of trusted Nextcloud servers |
|
| 61 | + * |
|
| 62 | + * @param int $id The ID of the trusted server to remove |
|
| 63 | + * @return DataResponse<Http::STATUS_OK, array{id: int}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{message: string}, array{}> |
|
| 64 | + * |
|
| 65 | + * 200: Server removed successfully |
|
| 66 | + * 404: Server not found at the given ID |
|
| 67 | + */ |
|
| 68 | + #[AuthorizedAdminSetting(settings: Admin::class)] |
|
| 69 | + #[ApiRoute(verb: 'DELETE', url: '/trusted-servers/{id}', requirements: ['id' => '\d+'])] |
|
| 70 | + public function removeServer(int $id): DataResponse { |
|
| 71 | + try { |
|
| 72 | + $this->trustedServers->getServer($id); |
|
| 73 | + } catch (\Exception $e) { |
|
| 74 | + throw new OCSNotFoundException($this->l->t('No server found with ID: %s', [$id])); |
|
| 75 | + } |
|
| 76 | 76 | |
| 77 | - try { |
|
| 78 | - $this->trustedServers->removeServer($id); |
|
| 79 | - return new DataResponse(['id' => $id]); |
|
| 80 | - } catch (\Exception $e) { |
|
| 81 | - $this->logger->error($e->getMessage(), ['e' => $e]); |
|
| 82 | - throw new OCSException($this->l->t('Could not remove server'), Http::STATUS_INTERNAL_SERVER_ERROR); |
|
| 83 | - } |
|
| 84 | - } |
|
| 77 | + try { |
|
| 78 | + $this->trustedServers->removeServer($id); |
|
| 79 | + return new DataResponse(['id' => $id]); |
|
| 80 | + } catch (\Exception $e) { |
|
| 81 | + $this->logger->error($e->getMessage(), ['e' => $e]); |
|
| 82 | + throw new OCSException($this->l->t('Could not remove server'), Http::STATUS_INTERNAL_SERVER_ERROR); |
|
| 83 | + } |
|
| 84 | + } |
|
| 85 | 85 | |
| 86 | - /** |
|
| 87 | - * List all trusted servers |
|
| 88 | - * |
|
| 89 | - * @return DataResponse<Http::STATUS_OK, list<array{id: int, status: int, url: string}>, array{}> |
|
| 90 | - * |
|
| 91 | - * 200: List of trusted servers |
|
| 92 | - */ |
|
| 93 | - #[AuthorizedAdminSetting(settings: Admin::class)] |
|
| 94 | - #[ApiRoute(verb: 'GET', url: '/trusted-servers')] |
|
| 95 | - public function getServers(): DataResponse { |
|
| 96 | - $servers = $this->trustedServers->getServers(); |
|
| 86 | + /** |
|
| 87 | + * List all trusted servers |
|
| 88 | + * |
|
| 89 | + * @return DataResponse<Http::STATUS_OK, list<array{id: int, status: int, url: string}>, array{}> |
|
| 90 | + * |
|
| 91 | + * 200: List of trusted servers |
|
| 92 | + */ |
|
| 93 | + #[AuthorizedAdminSetting(settings: Admin::class)] |
|
| 94 | + #[ApiRoute(verb: 'GET', url: '/trusted-servers')] |
|
| 95 | + public function getServers(): DataResponse { |
|
| 96 | + $servers = $this->trustedServers->getServers(); |
|
| 97 | 97 | |
| 98 | - // obfuscate the shared secret |
|
| 99 | - $servers = array_map(function ($server) { |
|
| 100 | - return [ |
|
| 101 | - 'url' => $server['url'], |
|
| 102 | - 'id' => $server['id'], |
|
| 103 | - 'status' => $server['status'], |
|
| 104 | - ]; |
|
| 105 | - }, $servers); |
|
| 98 | + // obfuscate the shared secret |
|
| 99 | + $servers = array_map(function ($server) { |
|
| 100 | + return [ |
|
| 101 | + 'url' => $server['url'], |
|
| 102 | + 'id' => $server['id'], |
|
| 103 | + 'status' => $server['status'], |
|
| 104 | + ]; |
|
| 105 | + }, $servers); |
|
| 106 | 106 | |
| 107 | - // return the list of trusted servers |
|
| 108 | - return new DataResponse($servers); |
|
| 109 | - } |
|
| 107 | + // return the list of trusted servers |
|
| 108 | + return new DataResponse($servers); |
|
| 109 | + } |
|
| 110 | 110 | |
| 111 | 111 | |
| 112 | - /** |
|
| 113 | - * Check if the server should be added to the list of trusted servers or not. |
|
| 114 | - */ |
|
| 115 | - #[AuthorizedAdminSetting(settings: Admin::class)] |
|
| 116 | - protected function checkServer(string $url): void { |
|
| 117 | - if ($this->trustedServers->isTrustedServer($url) === true) { |
|
| 118 | - throw new OCSException($this->l->t('Server is already in the list of trusted servers.'), Http::STATUS_CONFLICT); |
|
| 119 | - } |
|
| 112 | + /** |
|
| 113 | + * Check if the server should be added to the list of trusted servers or not. |
|
| 114 | + */ |
|
| 115 | + #[AuthorizedAdminSetting(settings: Admin::class)] |
|
| 116 | + protected function checkServer(string $url): void { |
|
| 117 | + if ($this->trustedServers->isTrustedServer($url) === true) { |
|
| 118 | + throw new OCSException($this->l->t('Server is already in the list of trusted servers.'), Http::STATUS_CONFLICT); |
|
| 119 | + } |
|
| 120 | 120 | |
| 121 | - if ($this->trustedServers->isNextcloudServer($url) === false) { |
|
| 122 | - throw new OCSNotFoundException($this->l->t('No server to federate with found')); |
|
| 123 | - } |
|
| 124 | - } |
|
| 121 | + if ($this->trustedServers->isNextcloudServer($url) === false) { |
|
| 122 | + throw new OCSNotFoundException($this->l->t('No server to federate with found')); |
|
| 123 | + } |
|
| 124 | + } |
|
| 125 | 125 | } |