From 76b63e5a9522762db5e282b8db5f7b1f271d8be4 Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Tue, 13 Feb 2024 13:24:52 +0200 Subject: [PATCH] feat(jans-auth-server): authz challenge should not require client_id and acr_values if valid device_session is provided #6867 (#7704) Signed-off-by: YuriyZ --- .../endpoints/authorization-challenge.md | 19 ++++ .../scripts/authorization-challenge.md | 20 ++++ .../AuthorizationChallenge.java | 10 ++ .../multi_step/AuthorizationChallenge.java | 10 ++ .../ws/rs/AuthorizationChallengeService.java | 21 +++- .../rs/AuthorizationChallengeServiceTest.java | 96 +++++++++++++++++++ .../EndSessionBackchannelRestServerTest.java | 15 +-- .../server/src/test/resources/testng.xml | 1 + 8 files changed, 181 insertions(+), 11 deletions(-) create mode 100644 jans-auth-server/server/src/test/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeServiceTest.java diff --git a/docs/admin/auth-server/endpoints/authorization-challenge.md b/docs/admin/auth-server/endpoints/authorization-challenge.md index ad6433c957c..2b0d4cedbac 100644 --- a/docs/admin/auth-server/endpoints/authorization-challenge.md +++ b/docs/admin/auth-server/endpoints/authorization-challenge.md @@ -141,7 +141,16 @@ Full sample script can be found [here](../../../script-catalog/authorization_cha Device session is optional. AS does not return it by default. It's possible to pass in request `use_device_session=true` which makes AS return it in error response. +If it is desired to use `device_session` and don't pass `client_id` (or other parameters) in next request, +it should be put in attributes of `device_session` object. +Example +```java +String clientId = context.getHttpRequest().getParameter("client_id"); +deviceSessionObject.getAttributes().getAttributes().put("client_id", clientId); +``` + +Full sample script can be found [here](../../../script-catalog/authorization_challenge/AuthorizationChallenge.java) ## Multi-step example @@ -224,6 +233,16 @@ In custom script it's easy to code what data has to be kept in `device_session`. if (StringUtils.isNotBlank(otp)) { deviceSessionObject.getAttributes().getAttributes().put(OTP_PARAMETER, otp); } + + String clientId = context.getHttpRequest().getParameter("client_id"); + if (StringUtils.isNotBlank(clientId)) { + deviceSessionObject.getAttributes().getAttributes().put("client_id", clientId); + } + + String acrValues = context.getHttpRequest().getParameter("acr_values"); + if (StringUtils.isNotBlank(acrValues)) { + deviceSessionObject.getAttributes().getAttributes().put("acr_values", acrValues); + } if (newSave) { deviceSessionService.persist(deviceSessionObject); diff --git a/docs/admin/developer/scripts/authorization-challenge.md b/docs/admin/developer/scripts/authorization-challenge.md index 16f89eaecc3..f674c2fd459 100644 --- a/docs/admin/developer/scripts/authorization-challenge.md +++ b/docs/admin/developer/scripts/authorization-challenge.md @@ -198,6 +198,16 @@ public class AuthorizationChallenge implements AuthorizationChallengeType { if (StringUtils.isNotBlank(password)) { deviceSessionObject.getAttributes().getAttributes().put(PASSWORD_PARAMETER, password); } + + String clientId = context.getHttpRequest().getParameter("client_id"); + if (StringUtils.isNotBlank(clientId)) { + deviceSessionObject.getAttributes().getAttributes().put("client_id", clientId); + } + + String acrValues = context.getHttpRequest().getParameter("acr_values"); + if (StringUtils.isNotBlank(acrValues)) { + deviceSessionObject.getAttributes().getAttributes().put("acr_values", acrValues); + } if (newSave) { deviceSessionService.persist(deviceSessionObject); @@ -392,6 +402,16 @@ public class AuthorizationChallenge implements AuthorizationChallengeType { if (StringUtils.isNotBlank(otp)) { deviceSessionObject.getAttributes().getAttributes().put(OTP_PARAMETER, otp); } + + String clientId = context.getHttpRequest().getParameter("client_id"); + if (StringUtils.isNotBlank(clientId)) { + deviceSessionObject.getAttributes().getAttributes().put("client_id", clientId); + } + + String acrValues = context.getHttpRequest().getParameter("acr_values"); + if (StringUtils.isNotBlank(acrValues)) { + deviceSessionObject.getAttributes().getAttributes().put("acr_values", acrValues); + } if (newSave) { deviceSessionService.persist(deviceSessionObject); diff --git a/docs/script-catalog/authorization_challenge/AuthorizationChallenge.java b/docs/script-catalog/authorization_challenge/AuthorizationChallenge.java index 02cb8783b8e..b5e06a32a75 100644 --- a/docs/script-catalog/authorization_challenge/AuthorizationChallenge.java +++ b/docs/script-catalog/authorization_challenge/AuthorizationChallenge.java @@ -135,6 +135,16 @@ private DeviceSession prepareDeviceSession(ExternalScriptContext context, Device deviceSessionObject.getAttributes().getAttributes().put(PASSWORD_PARAMETER, password); } + String clientId = context.getHttpRequest().getParameter("client_id"); + if (StringUtils.isNotBlank(clientId)) { + deviceSessionObject.getAttributes().getAttributes().put("client_id", clientId); + } + + String acrValues = context.getHttpRequest().getParameter("acr_values"); + if (StringUtils.isNotBlank(acrValues)) { + deviceSessionObject.getAttributes().getAttributes().put("acr_values", acrValues); + } + if (newSave) { deviceSessionService.persist(deviceSessionObject); } else { diff --git a/docs/script-catalog/authorization_challenge/multi_step/AuthorizationChallenge.java b/docs/script-catalog/authorization_challenge/multi_step/AuthorizationChallenge.java index 50ab9bdf29d..825fa8b3528 100644 --- a/docs/script-catalog/authorization_challenge/multi_step/AuthorizationChallenge.java +++ b/docs/script-catalog/authorization_challenge/multi_step/AuthorizationChallenge.java @@ -144,6 +144,16 @@ private DeviceSession prepareDeviceSession(ExternalScriptContext context, Device deviceSessionObject.getAttributes().getAttributes().put(OTP_PARAMETER, otp); } + String clientId = context.getHttpRequest().getParameter("client_id"); + if (StringUtils.isNotBlank(clientId)) { + deviceSessionObject.getAttributes().getAttributes().put("client_id", clientId); + } + + String acrValues = context.getHttpRequest().getParameter("acr_values"); + if (StringUtils.isNotBlank(acrValues)) { + deviceSessionObject.getAttributes().getAttributes().put("acr_values", acrValues); + } + if (newSave) { deviceSessionService.persist(deviceSessionObject); } else { diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeService.java index 560c20a46ad..56c27e7b004 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeService.java @@ -3,6 +3,7 @@ import com.google.common.collect.Maps; import io.jans.as.common.model.common.User; import io.jans.as.common.model.registration.Client; +import io.jans.as.common.model.session.DeviceSession; import io.jans.as.common.model.session.SessionId; import io.jans.as.model.authorize.AuthorizationChallengeResponse; import io.jans.as.model.authorize.AuthorizeErrorResponseType; @@ -114,7 +115,25 @@ public void prepareAuthzRequest(AuthzRequest authzRequest) { authzRequest.setScope(ServerUtil.urlDecode(authzRequest.getScope())); if (StringUtils.isNotBlank(authzRequest.getDeviceSession())) { - authzRequest.setDeviceSessionObject(deviceSessionService.getDeviceSession(authzRequest.getDeviceSession())); + final DeviceSession deviceSession = deviceSessionService.getDeviceSession(authzRequest.getDeviceSession()); + + authzRequest.setDeviceSessionObject(deviceSession); + if (deviceSession != null) { + final Map deviceSessionAttributes = deviceSession.getAttributes().getAttributes(); + + final String clientId = deviceSessionAttributes.get("client_id"); + if (StringUtils.isNotBlank(clientId) && StringUtils.isBlank(authzRequest.getClientId())) { + authzRequest.setClientId(clientId); + } + + String acrValues = deviceSession.getAttributes().getAcrValues(); + if (StringUtils.isBlank(acrValues)) { + acrValues = deviceSessionAttributes.get("acr_values"); + } + if (StringUtils.isNotBlank(acrValues) && StringUtils.isBlank(authzRequest.getAcrValues())) { + authzRequest.setAcrValues(acrValues); + } + } } } diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeServiceTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeServiceTest.java new file mode 100644 index 00000000000..a9e1d9d0a9c --- /dev/null +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeServiceTest.java @@ -0,0 +1,96 @@ +package io.jans.as.server.authorize.ws.rs; + +import io.jans.as.common.model.session.DeviceSession; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.server.audit.ApplicationAuditLogger; +import io.jans.as.server.model.authorize.ScopeChecker; +import io.jans.as.server.model.common.AuthorizationGrantList; +import io.jans.as.server.security.Identity; +import io.jans.as.server.service.CookieService; +import io.jans.as.server.service.RequestParameterService; +import io.jans.as.server.service.SessionIdService; +import io.jans.as.server.service.external.ExternalAuthorizationChallengeService; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.slf4j.Logger; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.when; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; + +/** + * @author Yuriy Z + */ +@Listeners(MockitoTestNGListener.class) +public class AuthorizationChallengeServiceTest { + + @InjectMocks + private AuthorizationChallengeService authorizationChallengeService; + + @Mock + private Logger log; + + @Mock + private AuthzRequestService authzRequestService; + + @Mock + private ApplicationAuditLogger applicationAuditLogger; + + @Mock + private AuthorizeRestWebServiceValidator authorizeRestWebServiceValidator; + + @Mock + private ScopeChecker scopeChecker; + + @Mock + private AuthorizationGrantList authorizationGrantList; + + @Mock + private AuthorizationChallengeValidator authorizationChallengeValidator; + + @Mock + private ExternalAuthorizationChallengeService externalAuthorizationChallengeService; + + @Mock + private ErrorResponseFactory errorResponseFactory; + + @Mock + private DeviceSessionService deviceSessionService; + + @Mock + private Identity identity; + + @Mock + private SessionIdService sessionIdService; + + @Mock + private AppConfiguration appConfiguration; + + @Mock + private RequestParameterService requestParameterService; + + @Mock + private CookieService cookieService; + + @Test + public void prepareAuthzRequest_whenClientIdStoredInAttributes_shouldPopulateAuthzRequest() { + final String deviceSessionId = "device_session_1234"; + + final DeviceSession deviceSession = new DeviceSession(); + deviceSession.setId(deviceSessionId); + deviceSession.getAttributes().getAttributes().put("client_id", "1234"); + + final AuthzRequest authzRequest = new AuthzRequest(); + authzRequest.setDeviceSession(deviceSessionId); + assertNull(authzRequest.getClientId()); + + when(deviceSessionService.getDeviceSession(deviceSessionId)).thenReturn(deviceSession); + + authorizationChallengeService.prepareAuthzRequest(authzRequest); + assertEquals("1234", authzRequest.getClientId()); + } +} diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/ws/rs/EndSessionBackchannelRestServerTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/ws/rs/EndSessionBackchannelRestServerTest.java index 89791f7770e..9f7d784583a 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/ws/rs/EndSessionBackchannelRestServerTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/ws/rs/EndSessionBackchannelRestServerTest.java @@ -6,7 +6,6 @@ package io.jans.as.server.ws.rs; -import com.google.common.collect.Lists; import io.jans.as.client.EndSessionRequest; import io.jans.as.client.RegisterRequest; import io.jans.as.model.authorize.AuthorizeResponseParam; @@ -18,14 +17,14 @@ import io.jans.as.model.util.StringUtils; import io.jans.as.server.BaseTest; import io.jans.as.server.model.TClientService; +import jakarta.ws.rs.client.Invocation; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.testng.annotations.Parameters; import org.testng.annotations.Test; -import jakarta.ws.rs.client.Invocation; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; @@ -33,11 +32,7 @@ import java.util.Map; import java.util.UUID; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.testng.Assert.*; /** * @author Yuriy Zabrovarnyy @@ -57,7 +52,7 @@ public void requestEndSessionStep1(final String redirectUris, final String postL io.jans.as.client.RegisterRequest registerRequest = new RegisterRequest(ApplicationType.WEB, "jans test app", StringUtils.spaceSeparatedToList(redirectUris)); registerRequest.setResponseTypes(Arrays.asList(ResponseType.TOKEN, ResponseType.ID_TOKEN)); registerRequest.setPostLogoutRedirectUris(Arrays.asList(postLogoutRedirectUri)); - registerRequest.setBackchannelLogoutUri(Lists.newArrayList(postLogoutRedirectUri)); + registerRequest.setBackchannelLogoutUri(postLogoutRedirectUri); registerRequest.addCustomAttribute("jansTrustedClnt", "true"); registerResponse = TClientService.register(registerRequest, getApiTagetURL(url)); diff --git a/jans-auth-server/server/src/test/resources/testng.xml b/jans-auth-server/server/src/test/resources/testng.xml index 567f77bb9ab..271b566d289 100644 --- a/jans-auth-server/server/src/test/resources/testng.xml +++ b/jans-auth-server/server/src/test/resources/testng.xml @@ -38,6 +38,7 @@ +