Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use OCA\Provisioning_API\Middleware\Exceptions\NotSubAdminException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCS\OCSException;
Expand Down Expand Up @@ -40,7 +41,7 @@ public function __construct(
*/
public function beforeController($controller, $methodName) {
// If AuthorizedAdminSetting, the check will be done in the SecurityMiddleware
if (!$this->isAdmin && !$this->reflector->hasAnnotation('NoSubAdminRequired') && !$this->isSubAdmin && !$this->reflector->hasAnnotation('AuthorizedAdminSetting')) {
if (!$this->isAdmin && !$this->reflector->hasAnnotation('NoSubAdminRequired') && !$this->isSubAdmin && !$this->reflector->hasAnnotationOrAttribute('AuthorizedAdminSetting', AuthorizedAdminSetting::class)) {
throw new NotSubAdminException();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@ public function testBeforeController(bool $subadminRequired, bool $isAdmin, bool
);

$this->reflector->method('hasAnnotation')
->willReturnCallback(function ($annotation) use ($subadminRequired, $hasSettingAuthorizationAnnotation) {
->willReturnCallback(function ($annotation) use ($subadminRequired) {
if ($annotation === 'NoSubAdminRequired') {
return !$subadminRequired;
}
return false;
});
$this->reflector->method('hasAnnotationOrAttribute')
->willReturnCallback(function ($annotation, $attribute) use ($hasSettingAuthorizationAnnotation) {
if ($annotation === 'AuthorizedAdminSetting') {
return $hasSettingAuthorizationAnnotation;
}
Expand Down
3 changes: 2 additions & 1 deletion apps/settings/lib/Middleware/SubadminMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
use OCP\Group\ISubAdmin;
Expand Down Expand Up @@ -44,7 +45,7 @@ private function isSubAdmin(): bool {

#[Override]
public function beforeController(Controller $controller, string $methodName): void {
if (!$this->reflector->hasAnnotation('NoSubAdminRequired') && !$this->reflector->hasAnnotation('AuthorizedAdminSetting')) {
if (!$this->reflector->hasAnnotation('NoSubAdminRequired') && !$this->reflector->hasAnnotationOrAttribute('AuthorizedAdminSetting', AuthorizedAdminSetting::class)) {
if (!$this->isSubAdmin()) {
throw new NotAdminException($this->l10n->t('Logged in account must be a sub admin'));
}
Expand Down
19 changes: 15 additions & 4 deletions apps/settings/tests/Middleware/SubadminMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OC\AppFramework\Utility\ControllerMethodReflector;
use OCA\Settings\Middleware\SubadminMiddleware;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Group\ISubAdmin;
use OCP\IL10N;
Expand Down Expand Up @@ -62,11 +63,16 @@ public function testBeforeControllerAsUserWithoutAnnotation(): void {
$this->expectException(NotAdminException::class);

$this->reflector
->expects($this->exactly(2))
->expects($this->exactly(1))
->method('hasAnnotation')
->willReturnMap([
['NoSubAdminRequired', false],
['AuthorizedAdminSetting', false],
]);
$this->reflector
->expects($this->exactly(1))
->method('hasAnnotationOrAttribute')
->willReturnMap([
['AuthorizedAdminSetting', AuthorizedAdminSetting::class, false],
]);

$this->subAdminManager
Expand Down Expand Up @@ -94,11 +100,16 @@ public function testBeforeControllerWithAnnotation(): void {

public function testBeforeControllerAsSubAdminWithoutAnnotation(): void {
$this->reflector
->expects($this->exactly(2))
->expects($this->exactly(1))
->method('hasAnnotation')
->willReturnMap([
['NoSubAdminRequired', false],
['AuthorizedAdminSetting', false],
]);
$this->reflector
->expects($this->exactly(1))
->method('hasAnnotationOrAttribute')
->willReturnMap([
['AuthorizedAdminSetting', AuthorizedAdminSetting::class, false],
]);

$this->subAdminManager
Expand Down
6 changes: 0 additions & 6 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1552,9 +1552,6 @@
</DeprecatedMethod>
</file>
<file src="apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php">
<DeprecatedInterface>
<code><![CDATA[protected]]></code>
</DeprecatedInterface>
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
<code><![CDATA[getAppValue]]></code>
Expand Down Expand Up @@ -2097,9 +2094,6 @@
</DeprecatedMethod>
</file>
<file src="apps/provisioning_api/lib/Middleware/ProvisioningApiMiddleware.php">
<DeprecatedInterface>
<code><![CDATA[IControllerMethodReflector]]></code>
</DeprecatedInterface>
<DeprecatedMethod>
<code><![CDATA[hasAnnotation]]></code>
<code><![CDATA[hasAnnotation]]></code>
Expand Down
9 changes: 4 additions & 5 deletions core/Middleware/TwoFactorMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

use Exception;
use OC\AppFramework\Http\Attributes\TwoFactorSetUpDoneRequired;
use OC\AppFramework\Middleware\MiddlewareUtils;
use OC\Authentication\Exceptions\TwoFactorAuthRequiredException;
use OC\Authentication\Exceptions\UserAlreadyLoggedInException;
use OC\Authentication\TwoFactorAuth\Manager;
Expand All @@ -23,6 +22,7 @@
use OCP\AppFramework\Http\Attribute\NoTwoFactorRequired;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\IControllerMethodReflector;
use OCP\Authentication\TwoFactorAuth\ALoginSetupController;
use OCP\IRequest;
use OCP\ISession;
Expand All @@ -36,7 +36,7 @@ public function __construct(
private Session $userSession,
private ISession $session,
private IURLGenerator $urlGenerator,
private MiddlewareUtils $middlewareUtils,
private IControllerMethodReflector $reflector,
private IRequest $request,
) {
}
Expand All @@ -46,9 +46,7 @@ public function __construct(
* @param string $methodName
*/
public function beforeController($controller, $methodName) {
$reflectionMethod = new ReflectionMethod($controller, $methodName);

if ($this->middlewareUtils->hasAnnotationOrAttribute($reflectionMethod, 'NoTwoFactorRequired', NoTwoFactorRequired::class)) {
if ($this->reflector->hasAnnotationOrAttribute('NoTwoFactorRequired', NoTwoFactorRequired::class)) {
// Route handler explicitly marked to work without finished 2FA are
// not blocked
return;
Expand All @@ -59,6 +57,7 @@ public function beforeController($controller, $methodName) {
return;
}

$reflectionMethod = new ReflectionMethod($controller, $methodName);
if ($controller instanceof TwoFactorChallengeController
&& $this->userSession->getUser() !== null
&& !$reflectionMethod->getAttributes(TwoFactorSetUpDoneRequired::class)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use OCP\ISession;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
use ReflectionMethod;

// Will close the session if the user session is ephemeral.
// Happens when the user logs in via the login flow v2.
Expand Down Expand Up @@ -61,12 +60,7 @@ public function beforeController(Controller $controller, string $methodName) {
return;
}

$reflectionMethod = new ReflectionMethod($controller, $methodName);
if (!empty($reflectionMethod->getAttributes(PublicPage::class))) {
return;
}

if ($this->reflector->hasAnnotation('PublicPage')) {
if ($this->reflector->hasAnnotationOrAttribute('PublicPage', PublicPage::class)) {
return;
}

Expand Down
13 changes: 2 additions & 11 deletions lib/private/AppFramework/Middleware/MiddlewareUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,10 @@ public function __construct(
* @param ReflectionMethod $reflectionMethod
* @param ?string $annotationName
* @param class-string<T> $attributeClass
* @return boolean
* @deprecated 34.0.0 call directly on the reflector
*/
public function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, ?string $annotationName, string $attributeClass): bool {
if (!empty($reflectionMethod->getAttributes($attributeClass))) {
return true;
}

if ($annotationName && $this->reflector->hasAnnotation($annotationName)) {
$this->logger->debug($reflectionMethod->getDeclaringClass()->getName() . '::' . $reflectionMethod->getName() . ' uses the @' . $annotationName . ' annotation and should use the #[' . $attributeClass . '] attribute instead');
return true;
}

return false;
return $this->reflector->hasAnnotationOrAttribute($annotationName, $attributeClass);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ public function __construct(
* @throws NotConfirmedException
*/
public function beforeController(Controller $controller, string $methodName) {
$reflectionMethod = new ReflectionMethod($controller, $methodName);

if (!$this->needsPasswordConfirmation($reflectionMethod)) {
if (!$this->needsPasswordConfirmation()) {
return;
}

Expand Down Expand Up @@ -79,6 +77,7 @@ public function beforeController(Controller $controller, string $methodName) {
return;
}

$reflectionMethod = new ReflectionMethod($controller, $methodName);
if ($this->isPasswordConfirmationStrict($reflectionMethod)) {
$authHeader = $this->request->getHeader('Authorization');
if (!str_starts_with(strtolower($authHeader), 'basic ')) {
Expand All @@ -101,18 +100,8 @@ public function beforeController(Controller $controller, string $methodName) {
}
}

private function needsPasswordConfirmation(ReflectionMethod $reflectionMethod): bool {
$attributes = $reflectionMethod->getAttributes(PasswordConfirmationRequired::class);
if (!empty($attributes)) {
return true;
}

if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) {
$this->logger->debug($reflectionMethod->getDeclaringClass()->getName() . '::' . $reflectionMethod->getName() . ' uses the @' . 'PasswordConfirmationRequired' . ' annotation and should use the #[PasswordConfirmationRequired] attribute instead');
return true;
}

return false;
private function needsPasswordConfirmation(): bool {
return $this->reflector->hasAnnotationOrAttribute('PasswordConfirmationRequired', PasswordConfirmationRequired::class);
}

private function isPasswordConfirmationStrict(ReflectionMethod $reflectionMethod): bool {
Expand Down
27 changes: 2 additions & 25 deletions lib/private/AppFramework/Middleware/SessionMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\ISession;
use ReflectionMethod;

class SessionMiddleware extends Middleware {
public function __construct(
Expand All @@ -28,18 +27,7 @@ public function __construct(
* @param string $methodName
*/
public function beforeController($controller, $methodName) {
/**
* Annotation deprecated with Nextcloud 26
*/
$hasAnnotation = $this->reflector->hasAnnotation('UseSession');
if ($hasAnnotation) {
$this->session->reopen();
return;
}

$reflectionMethod = new ReflectionMethod($controller, $methodName);
$hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
if ($hasAttribute) {
if ($this->reflector->hasAnnotationOrAttribute('UseSession', UseSession::class)) {
$this->session->reopen();
}
}
Expand All @@ -51,18 +39,7 @@ public function beforeController($controller, $methodName) {
* @return Response
*/
public function afterController($controller, $methodName, Response $response) {
/**
* Annotation deprecated with Nextcloud 26
*/
$hasAnnotation = $this->reflector->hasAnnotation('UseSession');
if ($hasAnnotation) {
$this->session->close();
return $response;
}

$reflectionMethod = new ReflectionMethod($controller, $methodName);
$hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
if ($hasAttribute) {
if ($this->reflector->hasAnnotationOrAttribute('UseSession', UseSession::class)) {
$this->session->close();
}

Expand Down
45 changes: 37 additions & 8 deletions lib/private/AppFramework/Utility/ControllerMethodReflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,39 @@
namespace OC\AppFramework\Utility;

use OCP\AppFramework\Utility\IControllerMethodReflector;
use Psr\Log\LoggerInterface;

/**
* Reads and parses annotations from doc comments
*/
class ControllerMethodReflector implements IControllerMethodReflector {
public $annotations = [];
private $types = [];
private $parameters = [];
public array $annotations = [];
private array $types = [];
private array $parameters = [];
private array $ranges = [];
private int $startLine = 0;
private string $file = '';
private ?\ReflectionMethod $reflectionMethod = null;

public function __construct(
private readonly LoggerInterface $logger,
) {
}

/**
* @param object $object an object or classname
* @param string $method the method which we want to inspect
*/
public function reflect($object, string $method) {
$reflection = new \ReflectionMethod($object, $method);
$this->startLine = $reflection->getStartLine();
$this->file = $reflection->getFileName();
$this->annotations = [];
$this->types = [];
$this->parameters = [];
$this->ranges = [];
$this->reflectionMethod = new \ReflectionMethod($object, $method);
$this->startLine = $this->reflectionMethod->getStartLine();
$this->file = $this->reflectionMethod->getFileName();

$docs = $reflection->getDocComment();
$docs = $this->reflectionMethod->getDocComment();

if ($docs !== false) {
// extract everything prefixed by @ and first letter uppercase
Expand Down Expand Up @@ -69,7 +80,7 @@ public function reflect($object, string $method) {
}
}

foreach ($reflection->getParameters() as $param) {
foreach ($this->reflectionMethod->getParameters() as $param) {
// extract type information from PHP 7 scalar types and prefer them over phpdoc annotations
$type = $param->getType();
if ($type instanceof \ReflectionNamedType) {
Expand Down Expand Up @@ -114,6 +125,24 @@ public function getParameters(): array {
return $this->parameters;
}

/**
* @template T
*
* @param class-string<T> $attributeClass
*/
public function hasAnnotationOrAttribute(?string $annotationName, string $attributeClass): bool {
if (!empty($this->reflectionMethod->getAttributes($attributeClass))) {
return true;
}

if ($annotationName && $this->hasAnnotation($annotationName)) {
$this->logger->debug($this->reflectionMethod->getDeclaringClass()->getName() . '::' . $this->reflectionMethod->getName() . ' uses the @' . $annotationName . ' annotation and should use the #[' . $attributeClass . '] attribute instead');
return true;
}

return false;
}

/**
* Check if a method contains an annotation
* @param string $name the name of the annotation
Expand Down
Loading
Loading