Skip to content

[SecurityBundle] support custom functions inside allow_if expressions #26263

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

Closed
wants to merge 1 commit into from

Conversation

dmaicher
Copy link
Contributor

@dmaicher dmaicher commented Feb 21, 2018

Q A
Branch? 2.7
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #23208
License MIT
Doc PR -

This is a follow up for #24309.


Problem:
The SecurityExtension was instantiating an \Symfony\Component\Security\Core\Authorization\ExpressionLanguage instance to parse the expressions defined in the access control config via allow_if: "...". So it used a pre-parsed SerializedParsedExpression instead of a raw Expression probably for performance reasons.

This has the serious drawback (and that's the bug) that it does not use the service 'security.expression_language' for this during the compiler pass. So any customizations for the service (like custom providers with functions) are completely ignored here on compile time.


Proposed fix
I propose to not dump parsed expressions at all and to leave this for run-time and caching.

I did some benchmarks and would like to hear your thoughts on this.
Using this small benchmark script (https://gist.github.com/dmaicher/ae506ff436a4647f3eaf7e59ec4c4aa1) I get the following numbers:

Using NullAdapter:

raw:    39.814957857132s 
parsed: 19.232628822327s

Using ArrayAdapter for caching there is almost no difference:

raw:    21.031932115555s 
parsed: 19.204074144363s

So on my laptop the difference for the NullAdapter case is roughly 20s for 500 thousand evaluations. Which means for one evaluation it's in the range of 40 microseconds or 0.04ms.

Maybe we can make use of cache.system for the expression language on 3.3+?

WDYT?

@nicolas-grekas
Copy link
Member

Should this really be done as a bug fix? Can't we consider it's a limitation for sure, but that's how it works for now? If you agree, then the cache warmup should be added in the PR on master.

@dmaicher
Copy link
Contributor Author

We can also consider it as a undocumented limitation and I can add it as a feature to master. I just find the current behavior kind of weird to be honest. But I don't really mind 😊

Should we use cache.system then for the expression language cache?

@nicolas-grekas
Copy link
Member

Should we use cache.system then for the expression language cache?

a dedicated pool instead, so userland items cannot collide with internals - see eg cache.validator

@stof
Copy link
Member

stof commented Feb 27, 2018

An option for 2.7 could be to keep trying to parse in the extension at compile time, but fallback to runtime parsing (using Expression instead of SerializedParsedExpression) in case of an invalid function being used (as this is the only kind of parse error which can change once registering custom functions). This way, only projects using a custom function in their allow_if expression would be affected by the slower code path.

@@ -36,10 +36,10 @@ public function process(ContainerBuilder $container)
}

// security
if ($container->has('security.access.expression_voter')) {
$definition = $container->findDefinition('security.access.expression_voter');
if ($container->has('security.expression_language')) {
Copy link
Member

Choose a reason for hiding this comment

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

why changing the registration logic ?

Copy link
Member

Choose a reason for hiding this comment

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

hmm, registering providers on the right service rather than relying on delegation still makes sense though, to ensure that the security.expression_language service is usable even if it is instantiated earlier than the ExpressionVoter due to injecting it elsewhere and due to voters being lazy-loaded).
We should probably even deprecated \Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter::addExpressionLanguageProvider in master as it only delegates the call and the ExpressionLanguage instance is always injected from the outside.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think adding it to security.expression_language directly makes more sense? Not even sure why ExpressionVoter::addExpressionLanguageProvider exists. Maybe I'm missing something?

If someone injects security.expression_language and uses it without the ExpressionVoter service ever being instantiated then the custom providers would be missing?

But if it makes merging etc harder I can also revert it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stof exactly 😊

👍 for deprecating it

Copy link
Member

Choose a reason for hiding this comment

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

is this change required for 2.7? if not, better revert, isn't it, and do it on master only?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No its not required 👍 will revert and apply to master together with the deprecation then

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nicolas-grekas removed 😉

@dmaicher
Copy link
Contributor Author

dmaicher commented Feb 27, 2018

An option for 2.7 could be to keep trying to parse in the extension at compile time, but fallback to runtime parsing (using Expression instead of SerializedParsedExpression) in case of an invalid function being used (as this is the only kind of parse error which can change once registering custom functions). This way, only projects using a custom function in their allow_if expression would be affected by the slower code path.

@stof yes I was also thinking about that.

We could catch SyntaxError but this can also mean the expression simply has a wrong syntax 🙈 So as a side effect instead of at compile time the error would show up on run-time. But I guess thats not a big problem? => actually that's the case already with my current fix here 😊

@dmaicher dmaicher force-pushed the fix-23208 branch 2 times, most recently from 69f6e3c to 537222f Compare February 27, 2018 19:34
@dmaicher
Copy link
Contributor Author

@stof @nicolas-grekas I updated it so we keep trying to parse the expression on compile time and we simply fallback to using a raw expression if it fails.

Let me know what you think 😉

I could create a separate PR for master to use a new cache pool + deprecate ExpressionVoter::addExpressionLanguageProvider.

@dmaicher dmaicher changed the title [SecurityBundle] do not pre-parse allow_if expressions to support custom functions [SecurityBundle] support custom functions inside allow_if expressions Feb 27, 2018
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

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

I'm wondering if the runtime error is an acceptable DX downgrade (vs the current compile time failure)
WDYT?

@dmaicher
Copy link
Contributor Author

dmaicher commented Feb 28, 2018

Hmm not sure its a big problem. I mean when using the FrameworkExtraBundle @Security annotation with an expression it also appears on run-time for example?

What do you think @stof ?

@dmaicher
Copy link
Contributor Author

dmaicher commented Mar 7, 2018

@nicolas-grekas @stof should I continue to work on this? Or should we close the issue as "won't fix"?

@nicolas-grekas
Copy link
Member

@dmaicher move as new feature on master?

@dmaicher
Copy link
Contributor Author

dmaicher commented Mar 19, 2018

@nicolas-grekas ok 😉 Will prepare a new PR soon for master that

  • does not pre-parse expressions anymore at compile-time
  • deprecates ExpressionVoter::addExpressionLanguageProvider
  • adds a cache pool for the security expression language

@dmaicher dmaicher closed this Mar 19, 2018
fabpot added a commit that referenced this pull request Apr 6, 2018
…low_if expressions (dmaicher)

This PR was merged into the 4.1-dev branch.

Discussion
----------

[SecurityBundle] allow using custom function inside allow_if expressions

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | no
| Fixed tickets | #23208
| License       | MIT
| Doc PR        | symfony/symfony-docs#9552

This is a follow-up for #26263

As discussed there I propose to allow using custom functions as a new feature only and thus targeting `master` here.

If we agree to move forward with this there are some todos:
- [x] fix tests
- [x] add cache warmer for allow_if expressions
- [x] update documentation to mention this new feature
- [x] update UPGRADE files

ping @nicolas-grekas @stof

Commits
-------

41552cd [SecurityBundle] allow using custom function inside allow_if expressions
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.

4 participants