Skip to content

Commit

Permalink
feat(jans-auth-server): passing custom parameters in the body of POST…
Browse files Browse the repository at this point in the history
… authorization request and ROPC #6141 (#6148)

* feat(jans-auth-server): passing custom parameters in the body of POST authorization request and ROPC #6141

#6141
Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

* feat(jans-auth-server): updated docs for ROPC at authorization endpoint #6141

#6141
Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

---------

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>
  • Loading branch information
yuriyz authored Sep 27, 2023
1 parent e77da4a commit 00673ae
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 4 deletions.
8 changes: 8 additions & 0 deletions docs/admin/developer/scripts/ropc.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ The ROPC script implements the [ResourceOwnerPasswordCredentialsType](https://gi

This script has been adapted from the Gluu Server [sample ROPC script](https://github.com/GluuFederation/community-edition-setup/blob/version_4.4.0/static/extension/resource_owner_password_credentials/resource_owner_password_credentials.py)

Main usage of script is at Token Endpoint.

However sometimes it can be useful to use ROPC custom script at Authorization Endpoint to avoid redirects and pages.
By default it is not enabled but it can be enabled if set `forceRopcInAuthorizationEndpoint` AS configuration property to `true`.

Also it is required to set `user` in context in custom script (`context.setUser(<user>)`). Without it authorization will go on in normal way (with pages and redirects).


### Script Type: Python

```python
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,9 @@ public class AppConfiguration implements Configuration {
@DocProperty(description = "Specifies if a token without implicit grant types is allowed")
private Boolean allowIdTokenWithoutImplicitGrantType;

@DocProperty(description = "Specifies whether to force ROPC custom script for Authorization Endpoint.", defaultValue = "false")
private Boolean forceRopcInAuthorizationEndpoint = false;

@DocProperty(description = "Lifetime of discovery cache", defaultValue = "60")
private int discoveryCacheLifetimeInMinutes = 60;

Expand Down Expand Up @@ -864,6 +867,15 @@ public class AppConfiguration implements Configuration {
@DocProperty(description = "Force Authentication Filtker to process OPTIONS request", defaultValue = "true")
private Boolean skipAuthenticationFilterOptionsMethod = true;

public Boolean getForceRopcInAuthorizationEndpoint() {
if (forceRopcInAuthorizationEndpoint == null) forceRopcInAuthorizationEndpoint = false;
return forceRopcInAuthorizationEndpoint;
}

public void setForceRopcInAuthorizationEndpoint(Boolean forceRopcInAuthorizationEndpoint) {
this.forceRopcInAuthorizationEndpoint = forceRopcInAuthorizationEndpoint;
}

public Map<String, String> getDateFormatterPatterns() {
return dateFormatterPatterns;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
import io.jans.as.server.service.*;
import io.jans.as.server.service.ciba.CibaRequestService;
import io.jans.as.server.service.external.ExternalPostAuthnService;
import io.jans.as.server.service.external.ExternalResourceOwnerPasswordCredentialsService;
import io.jans.as.server.service.external.ExternalSelectAccountService;
import io.jans.as.server.service.external.ExternalUpdateTokenService;
import io.jans.as.server.service.external.context.ExternalPostAuthnContext;
import io.jans.as.server.service.external.context.ExternalResourceOwnerPasswordCredentialsContext;
import io.jans.as.server.service.external.context.ExternalUpdateTokenContext;
import io.jans.as.server.service.external.session.SessionEvent;
import io.jans.as.server.service.external.session.SessionEventType;
Expand All @@ -65,6 +67,7 @@
import jakarta.ws.rs.core.SecurityContext;
import org.apache.commons.lang.StringUtils;
import org.jboss.resteasy.spi.NoLogWebApplicationException;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;

import java.net.URI;
Expand Down Expand Up @@ -162,6 +165,9 @@ public class AuthorizeRestWebServiceImpl implements AuthorizeRestWebService {
@Inject
private ExternalSelectAccountService externalSelectAccountService;

@Inject
private ExternalResourceOwnerPasswordCredentialsService externalResourceOwnerPasswordCredentialsService;

@Context
private HttpServletRequest servletRequest;

Expand Down Expand Up @@ -260,7 +266,8 @@ private Response requestAuthorization(AuthzRequest authzRequest) {

ResponseBuilder builder;

authzRequest.setCustomParameters(requestParameterService.getCustomParameters(QueryStringDecoder.decode(authzRequest.getHttpRequest().getQueryString())));
authzRequestService.setCustomParameters(authzRequest);


try {
builder = authorize(authzRequest);
Expand Down Expand Up @@ -335,6 +342,12 @@ private ResponseBuilder authorize(AuthzRequest authzRequest) throws AcrChangedEx
checkOfflineAccessScopes(responseTypes, authzRequest.getPromptList(), client, scopes);
checkResponseType(authzRequest, responseTypes, client);

User ropcUser = executeRopcIfRequired(user, new ExecutionContext(authzRequest.getHttpRequest(), authzRequest.getHttpResponse()));
if (ropcUser != null) {
user = ropcUser;
sessionUser = generatedAuthenticatedSessionForRopc(authzRequest, sessionUser, user);
}

AuthorizationGrant authorizationGrant = null;

if (user == null) {
Expand Down Expand Up @@ -479,6 +492,23 @@ private ResponseBuilder authorize(AuthzRequest authzRequest) throws AcrChangedEx
return builder;
}

@NotNull
private SessionId generatedAuthenticatedSessionForRopc(AuthzRequest authzRequest, SessionId sessionUser, User user) {
if (sessionUser == null) {
log.trace("Generating authenticated session.");
Map<String, String> genericRequestMap = getGenericRequestMap(authzRequest.getHttpRequest());

Map<String, String> parameterMap = Maps.newHashMap(genericRequestMap);
Map<String, String> requestParameterMap = requestParameterService.getAllowedParameters(parameterMap);
sessionUser = sessionIdService.generateAuthenticatedSessionId(authzRequest.getHttpRequest(), user.getDn(), authzRequest.getPrompt());
sessionUser.setSessionAttributes(requestParameterMap);

cookieService.createSessionIdCookie(sessionUser, authzRequest.getHttpRequest(), authzRequest.getHttpResponse(), false);
sessionIdService.updateSessionId(sessionUser);
}
return sessionUser;
}

private void addCustomHeaders(ResponseBuilder builder, AuthzRequest authzRequest) {
if (isTrue(appConfiguration.getCustomHeadersWithAuthorizationResponse())) {

Expand Down Expand Up @@ -1000,4 +1030,34 @@ private void processDeviceAuthorization(String userCode, User user) {

log.info("Granted device authorization request, user_code: {}, device_code: {}, grant_id: {}", userCode, cacheData.getDeviceCode(), deviceCodeGrant.getGrantId());
}

private User executeRopcIfRequired(User user, ExecutionContext executionContext) {
if (!appConfiguration.getForceRopcInAuthorizationEndpoint()) {
return null;
}

log.trace("Triggering ROPC at Authorization Endpoint (forced by 'forceRopcInAuthorizationEndpoint' configuration property)");

if (!externalResourceOwnerPasswordCredentialsService.isEnabled()) {
log.trace("Skip ROPC because no ROPC script found.");
return null;
}

final ExternalResourceOwnerPasswordCredentialsContext context = new ExternalResourceOwnerPasswordCredentialsContext(executionContext);
context.setUser(user);

if (externalResourceOwnerPasswordCredentialsService.executeExternalAuthenticate(context)) {
user = context.getUser();
if (user != null) {
log.trace("ROPC - User {} is authenticated successfully by external script.", user.getUserId());
return user;
} else {
log.trace("ROPC returned True but user is not set (set valid user in context.setUser(<user>))");
}
} else {
log.trace("ROPC script returned False.");
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.jans.as.model.jwt.JwtHeader;
import io.jans.as.model.jwt.JwtHeaderName;
import io.jans.as.model.token.JsonWebResponse;
import io.jans.as.model.util.QueryStringDecoder;
import io.jans.as.model.util.Util;
import io.jans.as.persistence.model.Par;
import io.jans.as.server.model.audit.Action;
Expand All @@ -48,6 +49,8 @@
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
Expand Down Expand Up @@ -571,4 +574,16 @@ public void createOauth2AuditLog(AuthzRequest authzRequest, Action action) {

authzRequest.setAuditLog(oAuth2AuditLog);
}

public void setCustomParameters(AuthzRequest authzRequest) {
final HttpServletRequest httpRequest = authzRequest.getHttpRequest();

authzRequest.setCustomParameters(requestParameterService.getCustomParameters(QueryStringDecoder.decode(httpRequest.getQueryString())));
if (authzRequest.getCustomParameters() == null) {
authzRequest.setCustomParameters(new HashMap<>());
}
if (HttpMethod.POST.endsWith(authzRequest.getHttpMethod())) {
requestParameterService.addCustomParameters(httpRequest, authzRequest.getCustomParameters());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package io.jans.as.server.service;

import com.google.common.collect.Lists;
import io.jans.as.common.model.session.SessionId;
import io.jans.as.model.authorize.AuthorizeRequestParam;
import io.jans.as.model.configuration.AppConfiguration;
import io.jans.as.model.configuration.AuthorizationRequestCustomParameter;
Expand All @@ -15,18 +16,21 @@
import io.jans.model.security.Identity;
import io.jans.util.Pair;
import io.jans.util.StringHelper;
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.json.JSONObject;
import org.slf4j.Logger;

import javax.annotation.Nonnull;
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.Map.Entry;

import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.BooleanUtils.isTrue;

/**
Expand Down Expand Up @@ -65,6 +69,9 @@ public class RequestParameterService {
AuthorizeRequestParam.SID,
DeviceAuthorizationService.SESSION_USER_CODE));

@Inject
private Logger log;

@Inject
private Identity identity;

Expand Down Expand Up @@ -235,4 +242,58 @@ public void getCustomParameters(JwtAuthorizationRequest jwtRequest, Map<String,
}
}
}

public Map<String, String> getCustomParameters(HttpServletRequest request) {
Map<String, String> customParameters = new HashMap<>();
addCustomParameters(request, customParameters);
return customParameters;
}

public void addCustomParameters(HttpServletRequest request, Map<String, String> customParameters) {
Set<AuthorizationRequestCustomParameter> authorizationRequestCustomAllowedParameters = appConfiguration
.getAuthorizationRequestCustomAllowedParameters();

if (authorizationRequestCustomAllowedParameters == null) {
log.trace("Skipped custom parameters because 'authorizationRequestCustomAllowedParameters' AS configuration is not set.");
return;
}

final Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
final String parameterName = parameterNames.nextElement();
if (!authorizationRequestCustomAllowedParameters.stream().map(AuthorizationRequestCustomParameter::getParamName).collect(toList()).contains(parameterName)) {
log.trace("Skipped '{}' as custom parameter (not defined in 'authorizationRequestCustomAllowedParameters')", parameterName);
continue;
}

final String parameterValue = request.getParameter(parameterName);
if (StringUtils.isNotBlank(parameterValue)) {
customParameters.put(parameterName, parameterValue);
}
}

log.trace("Custom parameters: {}", customParameters);
}

public void putCustomParametersIntoSession(SessionId sessionId, HttpServletRequest httpRequest) {
putCustomParametersIntoSession(sessionId, getCustomParameters(httpRequest));
}

public void putCustomParametersIntoSession(SessionId sessionId, Map<String, String> customParameters) {
if (sessionId == null || customParameters == null) {
return;
}

putCustomParametersIntoSession(sessionId.getSessionAttributes(), customParameters);
}

public void putCustomParametersIntoSession(Map<String, String> sessionAttributes, Map<String, String> customParameters) {
if (sessionAttributes == null || customParameters == null) {
return;
}

for (Map.Entry<String, String> entry : customParameters.entrySet()) {
sessionAttributes.put("custom_" + entry.getKey(), entry.getValue());
}
}
}

0 comments on commit 00673ae

Please sign in to comment.