[java-idp-oidc] branch main updated: JOIDC-128 - Support OAuth authorization requests
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Oct 7 10:24:09 UTC 2022
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=d8d8158a3f57c90dfba111647968f72116607c31
The following commit(s) were added to refs/heads/main by this push:
new d8d8158a JOIDC-128 - Support OAuth authorization requests
d8d8158a is described below
commit d8d8158a3f57c90dfba111647968f72116607c31
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Oct 7 13:22:49 2022 +0300
JOIDC-128 - Support OAuth authorization requests
https://shibboleth.atlassian.net/browse/JOIDC-128
UserInfo flow now requires openid -scope from the access token.
---
.../oidc/op/oauth2/profile/impl/ValidateScope.java | 35 ++++++++-
.../idp/flows/oidc/userinfo/userinfo-beans.xml | 11 +++
.../op/oauth2/profile/impl/ValidateScopeTest.java | 86 +++++++++++++++++++++-
.../plugin/oidc/op/profile/flow/UserInfoTest.java | 31 ++++++++
4 files changed, 157 insertions(+), 6 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateScope.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateScope.java
index 5b860f85..60505d59 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateScope.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateScope.java
@@ -77,6 +77,9 @@ public class ValidateScope extends AbstractOAuthAuthorizationResponseAction {
/** Strategy used to obtain the scope allowed for the client. */
@Nonnull private Function<ProfileRequestContext,Scope> allowedScopeLookupStrategy;
+ /** Strategy used to obtain the mandatory scope value value. */
+ @Nonnull private Function<ProfileRequestContext,Scope> mandatoryScopeLookupStrategy;
+
/** Strategy used to locate the {@link OIDCAuthenticationResponseTokenClaimsContext}. */
@Nonnull
private Function<ProfileRequestContext,OIDCAuthenticationResponseTokenClaimsContext>
@@ -88,6 +91,7 @@ public class ValidateScope extends AbstractOAuthAuthorizationResponseAction {
relyingPartyIdLookupStrategy = new RelyingPartyIdLookupFunction();
allowedScopeLookupStrategy = new ClientInfoScopeLookupFunction().compose(
new DefaultOIDCMetadataContextLookupFunction());
+ mandatoryScopeLookupStrategy = prc -> null;
tokenClaimsContextLookupStrategy =
new ChildContextLookup<>(OIDCAuthenticationResponseTokenClaimsContext.class).compose(
new OIDCAuthenticationResponseContextLookupFunction());
@@ -126,9 +130,21 @@ public class ValidateScope extends AbstractOAuthAuthorizationResponseAction {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
allowedScopeLookupStrategy = Constraint.isNotNull(strategy,
- "Allowed scope lookyp strategy cannot be null");
+ "Allowed scope lookup strategy cannot be null");
}
-
+
+ /**
+ * Set the strategy used to locate the mandatory scope value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setMandatoryScopeLookupStrategy(@Nonnull final Function<ProfileRequestContext,Scope> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ mandatoryScopeLookupStrategy = Constraint.isNotNull(strategy,
+ "Mandatory scope lookup strategy cannot be null");
+ }
+
/**
* Set the strategy used to locate the {@link OIDCAuthenticationResponseTokenClaimsContext} associated with a given
* {@link ProfileRequestContext}.
@@ -179,6 +195,21 @@ public class ValidateScope extends AbstractOAuthAuthorizationResponseAction {
return;
}
+ final Scope mandatoryScopes = mandatoryScopeLookupStrategy.apply(profileRequestContext);
+ if (mandatoryScopes != null && !mandatoryScopes.isEmpty()) {
+ if (requestedScopes == null || requestedScopes.isEmpty()) {
+ log.warn("{} Mendatory scope set to {} but none requested", getLogPrefix(), mandatoryScopes.toString());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+ for (final Scope.Value value : mandatoryScopes) {
+ if (!requestedScopes.contains(value.getValue())) {
+ log.warn("{} Mandatory scope {} is not requested", getLogPrefix(), value.getValue());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+ }
+ }
if (allowedScopes == null || allowedScopes.isEmpty()) {
log.debug("{} No allowed scope for client {}, nothing to do", getLogPrefix(), clientId);
return;
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
index 4af5b42f..4b0d4eb5 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
@@ -46,6 +46,17 @@
<property name="requestedScopeLookupStrategy">
<null/>
</property>
+ <property name="mandatoryScopeLookupStrategy">
+ <bean parent="shibboleth.Functions.Scripted">
+ <constructor-arg>
+ <value>
+ <![CDATA[
+ new com.nimbusds.oauth2.sdk.Scope(com.nimbusds.openid.connect.sdk.OIDCScopeValue.OPENID);
+ ]]>
+ </value>
+ </constructor-arg>
+ </bean>
+ </property>
</bean>
<bean id="SetRequestedClaimsToResponseContext"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateScopeTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateScopeTest.java
index eb36d64d..392e8041 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateScopeTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateScopeTest.java
@@ -62,15 +62,19 @@ public class ValidateScopeTest extends BaseOIDCResponseActionTest {
@BeforeMethod
private void init() throws ComponentInitializationException, URISyntaxException {
+ init(OIDCScopeValue.OPENID, OIDCScopeValue.EMAIL, OIDCScopeValue.OFFLINE_ACCESS);
+ }
+
+ private void init(Scope.Value... values) throws URISyntaxException, ComponentInitializationException {
action = new ValidateScope();
action.initialize();
final OIDCMetadataContext oidcCtx =
profileRequestCtx.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class, true);
metaData = new OIDCClientMetadata();
final Scope scope = new Scope();
- scope.add(OIDCScopeValue.OPENID);
- scope.add(OIDCScopeValue.EMAIL);
- scope.add(OIDCScopeValue.OFFLINE_ACCESS);
+ for (final Scope.Value value : values) {
+ scope.add(value);
+ }
metaData.setScope(scope);
metaData.setRedirectionURI(new URI("https://notmatching.org"));
final OIDCClientInformation information =
@@ -82,7 +86,7 @@ public class ValidateScopeTest extends BaseOIDCResponseActionTest {
respCtx.getSubcontext(OIDCAuthenticationResponseTokenClaimsContext.class, true);
tokenClaimsCtx.getClaims().setClaim("gen", "value1");
tokenClaimsCtx.getIdtokenClaims().setClaim("idtoken", "value2");
- tokenClaimsCtx.getUserinfoClaims().setClaim("userinfo", "value3");
+ tokenClaimsCtx.getUserinfoClaims().setClaim("userinfo", "value3");
}
/**
@@ -292,4 +296,78 @@ public class ValidateScopeTest extends BaseOIDCResponseActionTest {
Assert.assertNotNull(tokenClaimsCtx);
}
+ /**
+ * Test that action notices missing mandatory scope.
+ *
+ * @throws ComponentInitializationException
+ * @throws URISyntaxException
+ */
+ @Test
+ public void testMandatoryScopeMissing() throws ComponentInitializationException, URISyntaxException {
+
+ action = new ValidateScope();
+ action.setRequestedScopeLookupStrategy(null);
+ action.setMandatoryScopeLookupStrategy(prc -> Scope.parse("openid"));
+ action.initialize();
+
+ final UserInfoRequest req =
+ new UserInfoRequest(new URI("http://localhost"), Method.POST, new BearerAccessToken());
+ setUserInfoRequest(req);
+
+ final AuthorizeCodeClaimsSet claims = new AuthorizeCodeClaimsSet.Builder()
+ .setJWTID(idGenerator)
+ .setClientID(new ClientID("s6BhdRkqt3"))
+ .setIssuer("issuer")
+ .setPrincipal("userPrin")
+ .setSubject("subject")
+ .setIssuedAt(Instant.now())
+ .setExpiresAt(Instant.now())
+ .setAuthenticationTime(Instant.now())
+ .setRedirectURI(new URI("http://localhost"))
+ .setScope(Scope.parse("email"))
+ .setACR(new ACR("0"))
+ .build();
+ respCtx.setAuthorizationGrantClaimsSet(claims);
+
+ final Event event = action.execute(requestCtx);
+ ActionTestingSupport.assertEvent(event, "InvalidMessage");
+ }
+
+ /**
+ * Test that action deals requested openid scope correctly when it's not registered.
+ *
+ * @throws ComponentInitializationException
+ * @throws URISyntaxException
+ */
+ @Test
+ public void testOpenidScopeRequestedNotRegistered() throws ComponentInitializationException, URISyntaxException {
+ init(OIDCScopeValue.EMAIL);
+
+ action = new ValidateScope();
+ action.setRequestedScopeLookupStrategy(null);
+ action.initialize();
+
+ final UserInfoRequest req =
+ new UserInfoRequest(new URI("http://localhost"), Method.POST, new BearerAccessToken());
+ setUserInfoRequest(req);
+
+ final AuthorizeCodeClaimsSet claims = new AuthorizeCodeClaimsSet.Builder()
+ .setJWTID(idGenerator)
+ .setClientID(new ClientID("s6BhdRkqt3"))
+ .setIssuer("issuer")
+ .setPrincipal("userPrin")
+ .setSubject("subject")
+ .setIssuedAt(Instant.now())
+ .setExpiresAt(Instant.now())
+ .setAuthenticationTime(Instant.now())
+ .setRedirectURI(new URI("http://localhost"))
+ .setScope(Scope.parse("openid email"))
+ .setACR(new ACR("0"))
+ .build();
+ respCtx.setAuthorizationGrantClaimsSet(claims);
+
+ final Event event = action.execute(requestCtx);
+ ActionTestingSupport.assertEvent(event, "InvalidMessage");
+ }
+
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
index 37efb494..26718392 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
@@ -22,6 +22,7 @@ import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
+import org.opensaml.profile.action.EventIds;
import org.opensaml.storage.RevocationCache;
import org.opensaml.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -124,6 +125,36 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
Assert.assertNull(response.getUserInfoJWT());
}
+ @Test
+ public void testFailsWhenNoOpenidScopeRequested() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+ ComponentInitializationException, IOException {
+ final BearerAccessToken token = buildToken(clientId, subject, new Scope("profile"));
+ storeMetadata(storageService, clientId, "mockSecret", scope);
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testFailsWhenNoOpenidScopeRegistered() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+ ComponentInitializationException, IOException {
+ final BearerAccessToken token = buildToken(clientId, subject, new Scope("openid profile"));
+ storeMetadata(storageService, clientId, "mockSecret", new Scope("profile"));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testFailsWhenNoAnyScopeRegistered() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+ ComponentInitializationException, IOException {
+ final BearerAccessToken token = buildToken(clientId, subject, new Scope("openid profile"));
+ storeMetadata(storageService, clientId, "mockSecret", null);
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
@Test
public void testSuccessOnlySubjectWithLegacyToken() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list