Skip to content

Commit

Permalink
Merge pull request #2120 from Yoshani/par-improvements
Browse files Browse the repository at this point in the history
Improvements - Pushed Authorization Request support for IS
  • Loading branch information
chamathns committed Aug 2, 2023
2 parents ca22b3d + afb29b4 commit bf6b1a1
Show file tree
Hide file tree
Showing 25 changed files with 349 additions and 216 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ public final class OAuthConstants {
public static final String TENANT_DOMAIN_FROM_CONTEXT = "tenant_domain_from_context";
public static final String ALLOW_REQUEST_URI_AND_REQUEST_OBJECT_IN_REQUEST =
"allow_request_uri_and_request_object_in_request";
public static final String PAR_EXPIRY_TIME = "OAuth.PAR.ExpiryTime";

public static final String RENEW_TOKEN_WITHOUT_REVOKING_EXISTING_ALLOWED_GRANT_TYPES_CONFIG =
"OAuth.JWT.RenewTokenWithoutRevokingExisting.AllowedGrantTypes.AllowedGrantType";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2534,7 +2534,11 @@ private void handleOIDCRequestObject(OAuthMessage oAuthMessage, OAuthAuthzReques

private void validateRequestObjectParams(OAuthAuthzRequest oauthRequest) throws RequestObjectException {

// With in the same request it can not be used both request parameter and request_uri parameter.
/*
A single request cannot contain both 'request' and 'request_uri' parameters concurrently.
This validation is skipped when ALLOW_REQUEST_URI_AND_REQUEST_OBJECT_IN_REQUEST parameter is set to true,
where both request param and request_uri param will be present in the parameter map.
*/
if (Boolean.parseBoolean(oauthRequest.getParam(
OAuthConstants.ALLOW_REQUEST_URI_AND_REQUEST_OBJECT_IN_REQUEST))) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand All @@ -18,7 +18,11 @@

package org.wso2.carbon.identity.oauth.endpoint.par;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.interceptor.InInterceptors;
import org.apache.oltu.oauth2.common.error.OAuthError;
import org.json.JSONObject;
import org.wso2.carbon.identity.oauth.client.authn.filter.OAuthClientAuthenticatorProxy;
import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes;
Expand All @@ -27,7 +31,8 @@
import org.wso2.carbon.identity.oauth.par.common.ParConstants;
import org.wso2.carbon.identity.oauth.par.exceptions.ParClientException;
import org.wso2.carbon.identity.oauth.par.exceptions.ParCoreException;
import org.wso2.carbon.identity.oauth.par.model.ParAuthResponseData;
import org.wso2.carbon.identity.oauth.par.model.ParAuthData;
import org.wso2.carbon.identity.oauth2.bean.OAuthClientAuthnContext;
import org.wso2.carbon.identity.oauth2.dto.OAuth2ClientValidationResponseDTO;

import java.util.HashMap;
Expand Down Expand Up @@ -56,6 +61,10 @@
@InInterceptors(classes = OAuthClientAuthenticatorProxy.class)
public class OAuth2ParEndpoint {

private static final Log log = LogFactory.getLog(OAuth2ParEndpoint.class);

private static final String PAR_CLIENT_AUTH_ERROR = "Client Authentication Failed";

@POST
@Path("/")
@Consumes("application/x-www-form-urlencoded")
Expand All @@ -64,11 +73,12 @@ public Response par(@Context HttpServletRequest request, @Context HttpServletRes
MultivaluedMap<String, String> params) {

try {
checkClientAuthentication(request);
handleValidation(request, params);
Map<String, String> parameters = transformParams(params);
ParAuthResponseData parAuthResponseData =
EndpointUtil.getParAuthService().generateParAuthResponse(parameters);
return createAuthResponse(response, parAuthResponseData);
ParAuthData parAuthData =
EndpointUtil.getParAuthService().handleParAuthRequest(parameters);
return createAuthResponse(response, parAuthData);
} catch (ParClientException e) {
return handleParClientException(e);
} catch (ParCoreException e) {
Expand All @@ -91,13 +101,13 @@ private Map<String, String> transformParams(MultivaluedMap<String, String> param
return parameters;
}

private Response createAuthResponse(HttpServletResponse response, ParAuthResponseData parAuthResponseData) {
private Response createAuthResponse(HttpServletResponse response, ParAuthData parAuthData) {

response.setContentType(MediaType.APPLICATION_JSON);
net.minidev.json.JSONObject parAuthResponse = new net.minidev.json.JSONObject();
JSONObject parAuthResponse = new JSONObject();
parAuthResponse.put(OAuthConstants.OAuth20Params.REQUEST_URI,
ParConstants.REQUEST_URI_HEAD + parAuthResponseData.getReqUriUUID());
parAuthResponse.put(ParConstants.EXPIRES_IN, parAuthResponseData.getExpiryTime());
ParConstants.REQUEST_URI_PREFIX + parAuthData.getrequestURIReference());
parAuthResponse.put(ParConstants.EXPIRES_IN, parAuthData.getExpiryTime());
Response.ResponseBuilder responseBuilder = Response.status(HttpServletResponse.SC_CREATED);
return responseBuilder.entity(parAuthResponse.toString()).build();
}
Expand All @@ -115,16 +125,18 @@ private Response handleParClientException(ParClientException exception) {
} else {
responseBuilder = Response.status(HttpServletResponse.SC_BAD_REQUEST);
}
log.debug("Client error while handling the request: ", exception);
return responseBuilder.entity(parErrorResponse.toString()).build();
}

private Response handleParCoreException(ParCoreException parCoreException) {

JSONObject parErrorResponse = new JSONObject();
parErrorResponse.put(OAuthConstants.OAUTH_ERROR, OAuth2ErrorCodes.SERVER_ERROR);
parErrorResponse.put(OAuthConstants.OAUTH_ERROR_DESCRIPTION, parCoreException.getMessage());
parErrorResponse.put(OAuthConstants.OAUTH_ERROR_DESCRIPTION, "Internal Server Error.");

Response.ResponseBuilder respBuilder = Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
log.debug("Exception occurred when handling the request: ", parCoreException);
return respBuilder.entity(parErrorResponse.toString()).build();
}

Expand All @@ -146,4 +158,40 @@ private boolean isRequestUriProvided(MultivaluedMap<String, String> params) {

return params.containsKey(OAuthConstants.OAuth20Params.REQUEST_URI);
}

private void checkClientAuthentication(HttpServletRequest request) throws ParCoreException {

OAuthClientAuthnContext oAuthClientAuthnContext = getClientAuthnContext(request);
if (oAuthClientAuthnContext.isAuthenticated()) {
return;
}
if (StringUtils.isNotBlank(oAuthClientAuthnContext.getErrorCode())) {
if (OAuth2ErrorCodes.SERVER_ERROR.equals(oAuthClientAuthnContext.getErrorCode())) {
throw new ParCoreException(oAuthClientAuthnContext.getErrorCode(),
oAuthClientAuthnContext.getErrorMessage());
}
throw new ParClientException(oAuthClientAuthnContext.getErrorCode(),
oAuthClientAuthnContext.getErrorMessage());
}

throw new ParClientException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, "Client authentication required");
}

private OAuthClientAuthnContext getClientAuthnContext(HttpServletRequest request) {

Object oauthClientAuthnContextObj = request.getAttribute(OAuthConstants.CLIENT_AUTHN_CONTEXT);
if (oauthClientAuthnContextObj instanceof OAuthClientAuthnContext) {
return (OAuthClientAuthnContext) oauthClientAuthnContextObj;
}
return createNewOAuthClientAuthnContext();
}

private OAuthClientAuthnContext createNewOAuthClientAuthnContext() {

OAuthClientAuthnContext oAuthClientAuthnContext = new OAuthClientAuthnContext();
oAuthClientAuthnContext.setAuthenticated(false);
oAuthClientAuthnContext.setErrorMessage(PAR_CLIENT_AUTH_ERROR);
oAuthClientAuthnContext.setErrorCode(OAuthError.TokenResponse.INVALID_REQUEST);
return oAuthClientAuthnContext;
}
}
20 changes: 14 additions & 6 deletions components/org.wso2.carbon.identity.oauth.par/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
~ Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
~
~ WSO2 LLC. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -69,7 +69,10 @@
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.application.authentication.framework</artifactId>
</dependency>

<dependency>
<groupId>org.apache.ws.commons.axiom.wso2</groupId>
<artifactId>axiom</artifactId>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -104,23 +107,28 @@
org.wso2.carbon.identity.oauth.par.internal,
</Private-Package>
<Import-Package>
com.fasterxml.jackson.core; version="${com.fasterxml.jackson.version}"
com.fasterxml.jackson.core.type; version="${com.fasterxml.jackson.version}",
org.apache.axiom.om; version="${axiom.osgi.version.range}",
com.fasterxml.jackson.core.*; version="${com.fasterxml.jackson.version}"
com.fasterxml.jackson.databind; version="${com.fasterxml.jackson.databind.version.range}",
org.apache.commons.logging; version="${commons-logging.osgi.version.range}",
org.apache.commons.lang; version="${commons-lang.wso2.osgi.version.range}",
org.wso2.carbon.identity.oauth.common.*;version="${identity.inbound.auth.oauth.exp.pkg.version}"
org.apache.oltu.oauth2.common.*; version="${oltu.package.import.version.range}",
org.osgi.framework; version="${osgi.framework.imp.pkg.version.range}",
org.osgi.service.component; version="${osgi.service.component.imp.pkg.version.range}",
org.wso2.carbon.identity.application.authentication.framework.cache; version="${carbon.identity.framework.imp.pkg.version.range}",
org.wso2.carbon.identity.base; version="${carbon.identity.framework.imp.pkg.version.range}",
org.wso2.carbon.identity.core.*; version="${carbon.identity.framework.imp.pkg.version.range}",
javax.servlet; version="${imp.pkg.version.javax.servlet}",
org.wso2.carbon.context; version="${carbon.kernel.imp.pkg.version.range}",
javax.servlet.*; version="${imp.pkg.version.javax.servlet}",
com.fasterxml.jackson.databind; version="${com.fasterxml.jackson.databind.version.range}",
org.apache.oltu.oauth2.common.*; version="${oltu.package.import.version.range}",
</Import-Package>
<Export-Package>
!org.wso2.carbon.identity.oauth.par.internal,
org.wso2.carbon.identity.oauth.par.*;
version="${identity.inbound.auth.oauth.exp.pkg.version}",
</Export-Package>
<DynamicImport-Package>*</DynamicImport-Package>
</instructions>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.identity.oauth.par.dao.ParMgtDAO;
import org.wso2.carbon.identity.oauth.par.dao.ParMgtDAOImpl;
import org.wso2.carbon.identity.oauth.par.exceptions.ParCoreException;
import org.wso2.carbon.identity.oauth.par.model.ParRequestCacheEntry;
import org.wso2.carbon.identity.oauth.par.model.ParRequestDO;

import java.util.Map;
import java.util.Optional;

/**
* Caching layer for PAR Requests.
Expand All @@ -38,38 +40,43 @@ public class CacheBackedParDAO implements ParMgtDAO {
private final ParMgtDAOImpl parMgtDAO = new ParMgtDAOImpl();

@Override
public void persistParRequest(String reqUriUUID, String clientId, long scheduledExpiryTime,
Map<String, String> parameters) throws ParCoreException {
public void persistRequestData(String requestURIReference, String clientId, long expiresIn,
Map<String, String> parameters) throws ParCoreException {

ParRequestCacheEntry parRequestCacheEntry = new ParRequestCacheEntry(reqUriUUID, parameters,
scheduledExpiryTime);
parMgtDAO.persistParRequest(reqUriUUID, clientId, scheduledExpiryTime, parameters);
parCache.addToCache(reqUriUUID, parRequestCacheEntry);
ParRequestCacheEntry parRequestCacheEntry = new ParRequestCacheEntry(requestURIReference, parameters,
expiresIn, clientId);
parMgtDAO.persistRequestData(requestURIReference, clientId, expiresIn, parameters);
parCache.addToCache(requestURIReference, parRequestCacheEntry);
}

@Override
public ParRequestDO getParRequest(String reqUriUUID) throws ParCoreException {
public Optional<ParRequestDO> getRequestData(String requestURIReference) throws ParCoreException {

ParRequestCacheEntry parCacheRequest = parCache.getValueFromCache(reqUriUUID);
ParRequestDO parRequestDO;
if (parCacheRequest != null) {
String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain();
ParRequestCacheEntry parRequest = parCache.getValueFromCache(requestURIReference);
Optional<ParRequestDO> parRequestDO;
if (parRequest != null) {
if (log.isDebugEnabled()) {
log.debug("Cache miss for expiry time of local uuid: %s for tenant:%s " + reqUriUUID);
log.debug(
String.format("Cache hit for expiry time of local uuid: %s for tenant: %s ",
requestURIReference, tenantDomain));
}
return new ParRequestDO(parCacheRequest);
parRequestDO = Optional.of(new ParRequestDO(parRequest.getParams(), parRequest.getExpiresIn(),
parRequest.getClientId()));
} else {
if (log.isDebugEnabled()) {
log.debug("Cache hit for expiry time of uuid:%s for tenant:%s " + reqUriUUID);
log.debug(String.format("Cache miss for expiry time of uuid:%s for tenant: %s ",
requestURIReference, tenantDomain));
}
parRequestDO = parMgtDAO.getParRequest(reqUriUUID);
parRequestDO = parMgtDAO.getRequestData(requestURIReference);
}
return parRequestDO;
}

@Override
public void removeParRequest(String reqUriUUID) throws ParCoreException {
public void removeRequestData(String requestURIReference) throws ParCoreException {

parCache.clearCacheEntry(reqUriUUID);
parMgtDAO.removeParRequest(reqUriUUID);
parCache.clearCacheEntry(requestURIReference);
parMgtDAO.removeRequestData(requestURIReference);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public ParCache() {
}

/**
* Returns ParCache instance.
* Retrieve ParCache instance.
*
* @return instance of ParCache
* @return Instance of ParCache.
*/
public static ParCache getInstance() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,17 @@
* Contains the required constants for PAR feature.
*/
public class ParConstants {

public static final long EXPIRES_IN_DEFAULT_VALUE_IN_SEC = 60;
public static final long EXPIRES_IN_DEFAULT_VALUE = 60;
public static final long SEC_TO_MILLISEC_FACTOR = 1000;
public static final String UTC = "UTC";
public static final String EXPIRES_IN = "expires_in";
public static final String REQUEST_URI_HEAD = "urn:ietf:params:wso2is:request_uri:";
public static final String REQUEST_URI_IN_REQUEST_BODY_ERROR = "request.with.request_uri.not.allowed";
public static final String REQUEST_URI_PREFIX = "urn:ietf:params:oauth:request_uri:";
public static final String REQUEST_URI_IN_REQUEST_BODY_ERROR = "Request with request_uri not allowed.";
public static final String CACHE_NAME = "ParCache";
public static final String COL_LBL_JSON_PARAMS = "PARAMETERS";
public static final String COL_LBL_PARAMETERS = "PARAMETERS";
public static final String COL_LBL_SCHEDULED_EXPIRY = "SCHEDULED_EXPIRY";
public static final String COL_LBL_CLIENT_ID = "CLIENT_ID";

public static final String PAR = "PAR";

private ParConstants() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,29 @@
package org.wso2.carbon.identity.oauth.par.core;

import org.wso2.carbon.identity.oauth.par.exceptions.ParCoreException;
import org.wso2.carbon.identity.oauth.par.model.ParAuthResponseData;
import org.wso2.carbon.identity.oauth.par.model.ParAuthData;

import java.util.Map;


/**
* Provides authentication services.
* Provides the PAR services.
*/
public interface ParAuthService {

/**
* Creates PAR AuthenticationResponse.
* Creates PAR AuthenticationResponse by setting the values for the response to be generated from PAR endpoint.
*
* @return parAuthResponse that contains response data for request.
* @param parameters Map of parameters in the request.
* @return Object that contains response data for request.
*/
ParAuthResponseData generateParAuthResponse(Map<String, String> parameters) throws ParCoreException;
ParAuthData handleParAuthRequest(Map<String, String> parameters) throws ParCoreException;

/**
* Retrieve parameters from store.
* Retrieves the parameter map relevant to the provided request_uri from store after validating.
*
* @return parameter map for request.
* @param uuid UUID of the request.
* @param clientId Client ID of the request.
* @return Parameter map for request.
*/
Map<String, String> retrieveParams(String uuid, String clientId) throws ParCoreException;
}
Loading

0 comments on commit bf6b1a1

Please sign in to comment.