Skip to content

createEvaluationContext should defer lookup of Authentication #11187

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

Merged
merged 1 commit into from
May 18, 2022

Conversation

evgeniycheban
Copy link
Contributor

@evgeniycheban evgeniycheban commented May 7, 2022

createEvaluationContext should defer lookup of Authentication

  • Added createEvaluationContext method that accepts Supplier<Authentication>
  • Refactored classes that use EvaluationContext to use lazy initialization of Authentication

Closes gh-9667

@evgeniycheban evgeniycheban changed the title SecurityExpressionHandler#createEvaluationContext should defer lookup… createEvaluationContext should defer lookup of Authentication May 7, 2022
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 7, 2022
@evgeniycheban evgeniycheban force-pushed the gh-9667 branch 6 times, most recently from 2209536 to e0a8c98 Compare May 7, 2022 21:19
@jzheaux jzheaux self-assigned this May 11, 2022
@jzheaux jzheaux added in: core An issue in spring-security-core type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels May 11, 2022
Copy link
Contributor

@jzheaux jzheaux left a comment

Choose a reason for hiding this comment

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

Thanks, @evgeniycheban! I've left some feedback inline. Also, will you please rebase this against 5.8.x as I imagine that's were this change will land?

* Cannot be null.
*/
public SecurityExpressionRoot(Supplier<Authentication> authentication) {
this.authentication = new SupplierAuthentication(authentication);
Copy link
Contributor

Choose a reason for hiding this comment

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

The protected reference to Authentication obviously causes a bit of a problem. Given that there is getAuthentication() I wonder if it could be changed to private (possibly needs to wait until 6.0).

@rwinch, what do you think? I don't think it's still necessary for this property to be protected -- it only remains that way for backward compatibility. Can we introduce a breaking change in 5.8 that makes this field private so that it can be changed to a Supplier<Authentication>? Applications subclassing SecurityExpressionRoot would need to change this.authentication to getAuthentication().

Copy link
Member

Choose a reason for hiding this comment

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

I'm not certain of this, but I think that changing the member variable to private will break SpEL expressions that use #authentication

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, okay. I wondered if that was the case since I can access principal in a SpEL expression without there being a principal member variable. I'll do some experimenting and see.

Copy link
Contributor

Choose a reason for hiding this comment

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

I ran this test against SecurityExpressionRoot with protected Authentication authentication, private Authentication authentication, and private Supplier<Authentication> authentication:

@Test
public void authenticationIsCorrectlyResolved() {
	Authentication authentication = new TestingAuthenticationToken("user", "password");
	ExpressionParser parser = new SpelExpressionParser();
	EvaluationContext context = new StandardEvaluationContext(new SecurityExpressionRoot(authentication) {});
	Expression root = parser.parseExpression("authentication.name == 'user'");
	assertThat(root.getValue(context)).isEqualTo(true);
	Expression variable = parser.parseExpression("#authentication.name == 'user'");
	assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> variable.getValue(context));
}

And it passed for all three setups because the Expression uses the method resolver to resolve authentication. In no case could authentication be accessed as #authentication. Hypothetically, if we do need this, I think that we can do something like context.setVariable("authentication", authentication) during setup.

To remove noise, I ran a test against two custom objects unrelated to Security, other than to emulate the change from a protected field of Type A to a private field of Type B. In both classes, the public getter method remains the same:

@Test
public void valueFieldIsNotDirectlyReferenced() {
	ExpressionParser parser = new SpelExpressionParser();
	Expression method = parser.parseExpression("value.length() == 2");
	EvaluationContext privateField = new StandardEvaluationContext(new WithPrivateField());
	EvaluationContext protectedField = new StandardEvaluationContext(new WithProtectedField());
	assertThat(method.getValue(privateField)).isEqualTo(true);
	assertThat(method.getValue(protectedField)).isEqualTo(true);
	Expression field = parser.parseExpression("#value.length() == 2");
	assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> field.getValue(privateField));
	assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> field.getValue(protectedField));
}

private static class WithPrivateField {
	private Integer value = 13;

	public String getValue() {
		return String.valueOf(this.value);
	}
}

private static class WithProtectedField {
	protected String value = "13";

	public String getValue() {
		return this.value;
	}
}

The test again passes.

Also note that if I remove the method getValue() from WithProtectedField, the test fails with "Property or field 'value' cannot be found on object of type 'org.springframework.security.access.expression.AbstractSecurityExpressionHandlerTests$WithProtectedField' - maybe not public or not valid?". Only if I also make the field public does it pass.

Since the property in SecurityExpressionRoot is not public, I don't think it has any bearing on how expressions are evaluated.

Let me know if you see other rocks we should try and uncover.

@evgeniycheban
Copy link
Contributor Author

@jzheaux I've updated the PR and rebased this branch on top of 5.8.x, please take a look.
I've made changes to SecurityExpressionRoot.authentication now it’s private Supplier<Authentication> that can memorise the value.
Also I've added an implementation of new createEvaluationContext method for DefaultHttpSecurityExpressionHandler.

@evgeniycheban evgeniycheban requested a review from jzheaux May 13, 2022 23:29
@evgeniycheban evgeniycheban force-pushed the gh-9667 branch 3 times, most recently from 13f21e1 to 5fbe8b7 Compare May 17, 2022 19:59
Copy link
Contributor

@jzheaux jzheaux left a comment

Choose a reason for hiding this comment

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

Looking good, @evgeniycheban. I added one cleanup mention.

Additionally, are you able to add an implementation for DefaultMessageSecurityExpressionHandler?

- Added createEvaluationContext method that accepts Supplier<Authentication>
- Refactored classes that use EvaluationContext to use lazy initialization of Authentication

Closes spring-projectsgh-9667
@evgeniycheban evgeniycheban requested a review from jzheaux May 18, 2022 14:58
@jzheaux jzheaux added the status: duplicate A duplicate of another issue label May 18, 2022
@jzheaux jzheaux added this to the 5.8.0-M1 milestone May 18, 2022
@jzheaux jzheaux merged commit 362f155 into spring-projects:5.8.x May 18, 2022
@jzheaux
Copy link
Contributor

jzheaux commented May 18, 2022

Thanks, @evgeniycheban! This is now merged into 5.8.x as well as main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core An issue in spring-security-core status: duplicate A duplicate of another issue type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SecurityExpressionHandler#createEvaluationContext should defer lookup of Authentication
4 participants