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

[ELY-2574] Add ability to configure additional scope for authentication request #1925

Merged
merged 1 commit into from Mar 22, 2024

Conversation

PrarthonaPaul
Copy link
Contributor

@PrarthonaPaul PrarthonaPaul commented Jun 28, 2023

@fjuma
Copy link
Contributor

fjuma commented Jun 28, 2023

Thanks @PrarthonaPaul. Since this PR is against the wildfly-elytron project, the title, commit message, and description should be referencing an ELY issue.

@PrarthonaPaul PrarthonaPaul changed the title [WFLY-16532] Add ability to configure additional scope for authentica… [ELY-2574] Add ability to configure additional scope for authentication request Jun 28, 2023
@fjuma
Copy link
Contributor

fjuma commented Jun 28, 2023

@PrarthonaPaul OidcTest is currently failing, see https://github.com/wildfly-security/wildfly-elytron/actions/runs/5404971876/jobs/9819885760?pr=1925.

Were you able to get OidcTest running successfully locally?

@PrarthonaPaul
Copy link
Contributor Author

@PrarthonaPaul OidcTest is currently failing, see https://github.com/wildfly-security/wildfly-elytron/actions/runs/5404971876/jobs/9819885760?pr=1925.

Were you able to get OidcTest running successfully locally?

Yes I was able to run OidcTest as well as a full build with tests successfully.

@fjuma
Copy link
Contributor

fjuma commented Jun 28, 2023

@PrarthonaPaul OidcTest is currently failing, see https://github.com/wildfly-security/wildfly-elytron/actions/runs/5404971876/jobs/9819885760?pr=1925.
Were you able to get OidcTest running successfully locally?

Yes I was able to run OidcTest as well as a full build with tests successfully.

This is the failure from the CI log:

Tests run: 11, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 44.589 sec <<< FAILURE! - in org.wildfly.security.http.oidc.OidcTest
testScope(org.wildfly.security.http.oidc.OidcTest)  Time elapsed: 0.012 sec  <<< ERROR!
java.lang.RuntimeException: ELY23025: Must set 'auth-server-url' or 'provider-url'
	at org.wildfly.security.http.oidc.OidcClientConfigurationBuilder.internalBuild(OidcClientConfigurationBuilder.java:148)
	at org.wildfly.security.http.oidc.OidcClientConfigurationBuilder.build(OidcClientConfigurationBuilder.java:182)
	at org.wildfly.security.http.oidc.OidcTest.performAuthentication(OidcTest.java:175)
	at org.wildfly.security.http.oidc.OidcTest.testScope(OidcTest.java:167)

Might be good to check if maybe you have some local changes that weren't pushed.

@PrarthonaPaul
Copy link
Contributor Author

@PrarthonaPaul OidcTest is currently failing, see https://github.com/wildfly-security/wildfly-elytron/actions/runs/5404971876/jobs/9819885760?pr=1925.
Were you able to get OidcTest running successfully locally?

Yes I was able to run OidcTest as well as a full build with tests successfully.

This is the failure from the CI log:

Tests run: 11, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 44.589 sec <<< FAILURE! - in org.wildfly.security.http.oidc.OidcTest
testScope(org.wildfly.security.http.oidc.OidcTest)  Time elapsed: 0.012 sec  <<< ERROR!
java.lang.RuntimeException: ELY23025: Must set 'auth-server-url' or 'provider-url'
	at org.wildfly.security.http.oidc.OidcClientConfigurationBuilder.internalBuild(OidcClientConfigurationBuilder.java:148)
	at org.wildfly.security.http.oidc.OidcClientConfigurationBuilder.build(OidcClientConfigurationBuilder.java:182)
	at org.wildfly.security.http.oidc.OidcTest.performAuthentication(OidcTest.java:175)
	at org.wildfly.security.http.oidc.OidcTest.testScope(OidcTest.java:167)

Might be good to check if maybe you have some local changes that weren't pushed.

rerunning tests right now.
Implemented the other changes. Will push once I figure out what is causing the error.
Thank you for the feedback!

@fjuma
Copy link
Contributor

fjuma commented Jul 18, 2023

@PrarthonaPaul Looks like the commits need to be cleaned up a bit so that they all reference the ELY issue and looks like things can be squashed too.

Copy link
Contributor

@fjuma fjuma left a comment

Choose a reason for hiding this comment

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

Thanks for the updates @PrarthonaPaul! I've added some feedback.

public static OidcClientConfiguration buildWithoutUnsupportedAttributes(String unsupportedAttributesParam, InputStream is) {
OidcJsonConfiguration oidcJsonConfiguration = loadOidcJsonConfiguration(is);
try {
failIfUnsupportedAttribute(unsupportedAttributesParam, oidcJsonConfiguration);
Copy link
Contributor

Choose a reason for hiding this comment

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

Just wondering if it's possible to detect the unsupported attribute somehow during loadOidcJsonConfiguration rather than after the OidcJsonConfiguration has been created?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we want it to be inside loadOidcJsonConfig to save parsing time, then don't think so, because loadOidcJsonConfig() uses adapterConfig = mapper.readValue(is, OidcJsonConfiguration.class);. And since scope is a jsonProperty defined in the OidcJsonConfiguration class, it would not fail.
But if we want to do something like

adapterConfig = mapper.readValue(is, OidcJsonConfiguration.class);
check for scope inside adapterConfig and fail if scope is not null

then yes, we can do that.
But I am not sure if that simplifies the code any more. The reason why I added a new function is so that we still have a build(InputStream is) function.
If we handle the scope check inside loadOidcJsonConfiguration, then we will instead need to add something like loadOidcJsonConfigurationWithoutUnsuppportedAttributes(InputStream is, String unsupportedOidcAttributes)
Please feel free to let me know if that is cleaner though. I am okay with changing it.

@fjuma
Copy link
Contributor

fjuma commented Mar 20, 2024

@PrarthonaPaul I think hold off for a bit on making updates based on my latest comments. Am looking closer to see if there's a different way we could handle this.

@@ -66,7 +67,7 @@ public void contextInitialized(ServletContextEvent sce) {
if (is == null) {
oidcClientConfiguration = new OidcClientConfiguration();
} else {
oidcClientConfiguration = OidcClientConfigurationBuilder.build(is);
oidcClientConfiguration = OidcClientConfigurationBuilder.buildWithoutUnsupportedAttributes(is, servletContext.getInitParameter(JSON_CONFIG_UNSUPPORTED_ATTRIBUTE_PARAM));
Copy link
Contributor

Choose a reason for hiding this comment

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

@PrarthonaPaul I've looked more closely into this and have an alternative suggestion for us to consider.

Instead of needing to update Elytron so that it can cope with attributes that are not supported by the default stability level in WildFly, I think we can add handling in OidcActivationProcessor in the elytron-oidc-client subsystem. In particular, I think we could do the following:

  • In OidcActivationProcessor, we can get the WEB-INF/oidc.json file from the deployment as follows: deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT).getRoot().getChild("WEB-INF/oidc.json").
  • That gives us a VirtualFile that we could then read the contents of to end up with a String.
  • We could then add handling to OidcActivationProcessor#deploy such that if the stability level isn't the community stability level (i.e., ! deploymentUnit.getStability().enables(Stability.COMMUNITY)), then if the contents contain the specific attributes that are only available at the community stability level, deployment should fail. Otherwise, the String can be used to set the JSON_CONFIG_CONTEXT_PARAM servlet context param so that when the OidcClientConfiguration is being built, we won't read from the oidc.json file a second time, we'll just make use of the String we already have.

WDYT of this approach?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is cleaner if in WildFly we can do the stability level validation as stability levels are a WildFly concern not an Elytron concern.

If I am reading this suggestion correctly are we talking about being able to parse from the String directly?

Maybe as a follow up later on another option could be when the Processor in WildFly calls these APIs it can also pass in some predicate for validation?

But for now +1 on constraining to WildFly if we can.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, this suggestion is to parse from the String directly in OidcActivationProcessor in WildFly.

As a follow up, we could try to pass in a predicate that would be used for validation. We'd likely need to create a class that extends com.fasterxml.jackson.core.JsonFactory to be able to make use of the predicate when parsing in OidcClientConfigurationBuilder#loadOidcJsonConfiguration.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we move the predicate option to a Jira issue we can follow up later and decide if we want to do that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've created https://issues.redhat.com/browse/ELY-2737 to track this.

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 have updated the code for handling unsupported attributes for deployment config and removed the old code.

String scopes = jwtClaims.getClaimValueAsString("scope");
if (scopes != null) {
if (scopes.contains("email")) {
assertTrue(jwtClaims.getClaimValueAsString("email_verified").contains(String.valueOf(KeycloakConfiguration.EMAIL_VERIFIED)));
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems that Keycloak will set the email and profile scopes by default so just wondering if checking only email_verified and profile are actually enough to successfully test that the configured scopes have been set properly. As an example, I tried tweaking testMultipleScopeValue locally to only set the openid scope. I would have expected the test to fail in this case however it actually ended up passing. Is there a claim that we could check that would only be set if a specific scope other than the default ones has been configured?

Copy link
Contributor

@fjuma fjuma Mar 20, 2024

Choose a reason for hiding this comment

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

Looks like the address claim is a candidate for a claim that would confirm that the configured scopes are being used as expected, i.e., we could update tests to also specify the address scope. Then, the JWT claims will include the address claim as well. This claim isn't included by default so this would confirm that the configured scopes are actually getting used as expected.

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 don't think we can do address, because org.keycloak.representations.idm.UserRepresentation
does not have a way to set that. Plus when I added address as a scope, the request timed out.
I saw this earlier with this API as well when I wanted to set offline_access as one of the scopes. It is kind of limiting in that sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

However, I can change the default list of scopes for the client to just OpenID, so that the results of those scopes are only included if the config asks for it.

Copy link
Contributor

@fjuma fjuma Mar 21, 2024

Choose a reason for hiding this comment

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

I tried adding address locally and was able to see the address added in the claims. So even if we can't actually set a value for it, we can still verify that it's present in the claims. This allows us to verify that setting the scope value actually resulted in the correct behaviour. With the test as is right now, we can't be sure that setting the scope actually had an effect since the current claims that are being tested will be present regardless of the scope configuration.

Copy link
Contributor

Choose a reason for hiding this comment

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

However, I can change the default list of scopes for the client to just OpenID, so that the results of those scopes are only included if the config asks for it.

Yes another option would be to change the default list of scopes if that's easy to do. I quickly tried that yesterday but wasn't able to get things working as expected but maybe I was missing something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I am trying that now and I am getting a class cast exception.
I am looking more into it to see what is causing that.

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 was able to fix this. I added openid as the default scope and the others needed to be added as optional scopes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for this update! The tests look great now!

One last comment (that won't block merging either) is that right now we're checking if the scope from the JWT claims contains a certain value and then checking that value. Instead, it would be better to use the expectedScope String instead. It should be possible to do that by passing the expectedScope to getCallbackHandler instead of passing the boolean value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

@PrarthonaPaul PrarthonaPaul force-pushed the WFLY-16532 branch 5 times, most recently from d582ef7 to a3fa083 Compare March 21, 2024 18:10
@PrarthonaPaul PrarthonaPaul force-pushed the WFLY-16532 branch 2 times, most recently from 7eded8d to 6353159 Compare March 21, 2024 20:07
Copy link
Contributor

@fjuma fjuma left a comment

Choose a reason for hiding this comment

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

Thanks very much @PrarthonaPaul!

@fjuma fjuma added the +1 FJ label Mar 21, 2024
@fjuma fjuma requested a review from darranl March 22, 2024 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants