Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(middleware): Migrate BruteForceProtection annotation to PHP Attribute and allow multiple #36928

Merged
merged 2 commits into from
Mar 16, 2023

Conversation

nickvergessen
Copy link
Member

@nickvergessen nickvergessen commented Feb 28, 2023

Previously (and still supported)

	/**
	 * @BruteForceProtection(action=login)
	 */
	public function testMethodWithAnnotation() {

New syntax

	#[BruteForceProtection(action: 'login')]
	public function testMethodWithAttribute() {

Multiple attributes now supported

By specifying an 'action' in the throttle($metadata) only the dedicated attribute will trigger it's BFP, if none is specified all will throttle.
This is useful e.g. in controllers that need to protect different data, e.g. Talk has the tokens and the signaling secret in the same method and needs to do a lot of manual work instead of just calling throttle on the response:
https://github.com/nextcloud/spreed/blob/29a442cf86d49b426ecd47776c86ab6f6de5da58/lib/Controller/SignalingController.php#L154-L179

	#[BruteForceProtection(action: 'token')]
	#[BruteForceProtection(action: 'password')]
	public function testMethodWithMultipleAttributes() {

🚧 ToDo

  • Allow as attribute
  • Add multi support
  • Log debug when the request was throttled, but there was no Annotation nor matching Attribute
  • Add a header to the response when the request is throttled(?)

Checklist

@nickvergessen nickvergessen added this to the Nextcloud 27 milestone Feb 28, 2023
@nickvergessen nickvergessen self-assigned this Feb 28, 2023
@nickvergessen nickvergessen added the pending documentation This pull request needs an associated documentation update label Feb 28, 2023
…ibute and allow multiple

Signed-off-by: Joas Schilling <coding@schilljs.com>
Signed-off-by: Joas Schilling <coding@schilljs.com>
@nickvergessen nickvergessen force-pushed the techdebt/noid/bruteforce-protection-attribute branch from c26dc84 to 2b49861 Compare March 8, 2023 11:09
@nickvergessen nickvergessen added 3. to review Waiting for reviews and removed 2. developing Work in progress labels Mar 8, 2023
@nickvergessen nickvergessen marked this pull request as ready for review March 8, 2023 11:09
@nickvergessen
Copy link
Member Author

Documentation update at nextcloud/documentation#9742

@@ -68,18 +68,55 @@ public function beforeController($controller, $methodName) {
if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
$this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to add hasAttribute / getAttributes to ControllerMethodReflector ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THought about that too. I'm still up for it as a follow up, to combine the logic our for UseSession and this one.
Just thought it's easier to first get this in and then do more rework.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m always afraid the follow-up never comes in these cases :-)

We should try to make the new way as easy to use as the previous one if we want the code to get migrated at some point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m always afraid the follow-up never comes in these cases

I guess you have a point, at the same time there is the RateLimit annotation waiting for me as a task this and next month, so will come.

Comment on lines +92 to +119
if ($response->isThrottled()) {
if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
$ip = $this->request->getRemoteAddress();
$this->throttler->sleepDelay($ip, $action);
$this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
} else {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);

if (!empty($attributes)) {
$ip = $this->request->getRemoteAddress();
$metaData = $response->getThrottleMetadata();

foreach ($attributes as $attribute) {
/** @var BruteForceProtection $protection */
$protection = $attribute->newInstance();
$action = $protection->getAction();

if (!isset($metaData['action']) || $metaData['action'] === $action) {
$this->throttler->sleepDelay($ip, $action);
$this->throttler->registerAttempt($action, $ip, $metaData);
}
}
} else {
$this->logger->debug('Response for ' . get_class($controller) . '::' . $methodName . ' got bruteforce throttled but has no annotation nor attribute defined.');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a good piece of code to refactor and split the responsibility at separated method to make the reading more easy.

Maybe anything like:

->parseBruteForceAnnotation
->parseBruteForceAttributes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants