Skip to content

Commit

Permalink
Merge pull request #12 from ChamathNS/id-token-validation-fix
Browse files Browse the repository at this point in the history
Add Id token validation fix and HTTP Session Wrapper for the OIDC Manger.
  • Loading branch information
darshanasbg authored Nov 12, 2020
2 parents 28db80f + 8935b73 commit 52b706f
Show file tree
Hide file tree
Showing 23 changed files with 1,389 additions and 145 deletions.
13 changes: 13 additions & 0 deletions io.asgardio.java.oidc.sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.jadler</groupId>
<artifactId>jadler-core</artifactId>
</dependency>
<dependency>
<groupId>net.jadler</groupId>
<artifactId>jadler-jetty</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package io.asgardio.java.oidc.sdk;

import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.jwt.SignedJWT;
Expand All @@ -40,14 +41,20 @@
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import io.asgardio.java.oidc.sdk.bean.AuthenticationInfo;
import com.nimbusds.openid.connect.sdk.Nonce;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
import io.asgardio.java.oidc.sdk.bean.RequestContext;
import io.asgardio.java.oidc.sdk.bean.SessionContext;
import io.asgardio.java.oidc.sdk.bean.User;
import io.asgardio.java.oidc.sdk.config.model.OIDCAgentConfig;
import io.asgardio.java.oidc.sdk.exception.SSOAgentClientException;
import io.asgardio.java.oidc.sdk.exception.SSOAgentException;
import io.asgardio.java.oidc.sdk.exception.SSOAgentServerException;
import io.asgardio.java.oidc.sdk.request.OIDCRequestBuilder;
import io.asgardio.java.oidc.sdk.request.OIDCRequestResolver;
import io.asgardio.java.oidc.sdk.request.model.AuthenticationRequest;
import io.asgardio.java.oidc.sdk.request.model.LogoutRequest;
import io.asgardio.java.oidc.sdk.validators.IDTokenValidator;
import net.minidev.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.Level;
Expand All @@ -66,13 +73,13 @@
/**
* OIDC manager implementation.
*/
public class OIDCManagerImpl implements OIDCManager {
public class DefaultOIDCManager implements OIDCManager {

private static final Logger logger = LogManager.getLogger(OIDCManagerImpl.class);
private static final Logger logger = LogManager.getLogger(DefaultOIDCManager.class);

private OIDCAgentConfig oidcAgentConfig;

public OIDCManagerImpl(OIDCAgentConfig oidcAgentConfig) throws SSOAgentClientException {
public DefaultOIDCManager(OIDCAgentConfig oidcAgentConfig) throws SSOAgentClientException {

validateConfig(oidcAgentConfig);
this.oidcAgentConfig = oidcAgentConfig;
Expand All @@ -82,36 +89,38 @@ public OIDCManagerImpl(OIDCAgentConfig oidcAgentConfig) throws SSOAgentClientExc
* {@inheritDoc}
*/
@Override
public void sendForLogin(HttpServletRequest request, HttpServletResponse response, String state)
public RequestContext sendForLogin(HttpServletRequest request, HttpServletResponse response)
throws SSOAgentException {

OIDCRequestBuilder requestBuilder = new OIDCRequestBuilder(oidcAgentConfig);
String authorizationRequest = requestBuilder.buildAuthorizationRequest(state);
AuthenticationRequest authenticationRequest = requestBuilder.buildAuthenticationRequest();

try {
response.sendRedirect(authorizationRequest);
response.sendRedirect(authenticationRequest.getAuthenticationRequestURI().toString());
} catch (IOException e) {
throw new SSOAgentException(e.getMessage(), e);
}
return authenticationRequest.getRequestContext();
}

/**
* {@inheritDoc}
*/
@Override
public AuthenticationInfo handleOIDCCallback(HttpServletRequest request, HttpServletResponse response)
throws SSOAgentException {
public SessionContext handleOIDCCallback(HttpServletRequest request, HttpServletResponse response,
RequestContext requestContext) throws SSOAgentException {

OIDCRequestResolver requestResolver = new OIDCRequestResolver(request, oidcAgentConfig);
AuthenticationInfo authenticationInfo = new AuthenticationInfo();
SessionContext sessionContext = new SessionContext();
Nonce nonce = requestContext.getNonce();

try {
if (!requestResolver.isError() && requestResolver.isAuthorizationCodeResponse()) {
logger.log(Level.TRACE, "Handling the OIDC Authorization response.");
boolean isAuthenticated = handleAuthentication(request, authenticationInfo);
boolean isAuthenticated = handleAuthentication(request, sessionContext, nonce);
if (isAuthenticated) {
logger.log(Level.TRACE, "Authentication successful. Redirecting to the target page.");
return authenticationInfo;
return sessionContext;
}
}

Expand All @@ -127,8 +136,7 @@ public AuthenticationInfo handleOIDCCallback(HttpServletRequest request, HttpSer
* {@inheritDoc}
*/
@Override
public void logout(AuthenticationInfo authenticationInfo, HttpServletResponse response, String state)
throws SSOAgentException {
public RequestContext logout(SessionContext sessionContext, HttpServletResponse response) throws SSOAgentException {

if (oidcAgentConfig.getPostLogoutRedirectURI() == null) {
logger.info("postLogoutRedirectURI is not configured. Using the callbackURL instead.");
Expand All @@ -137,17 +145,19 @@ public void logout(AuthenticationInfo authenticationInfo, HttpServletResponse re
}

OIDCRequestBuilder requestBuilder = new OIDCRequestBuilder(oidcAgentConfig);
String logoutRequest = requestBuilder.buildLogoutRequest(authenticationInfo, state);
LogoutRequest logoutRequest = requestBuilder.buildLogoutRequest(sessionContext);

try {
response.sendRedirect(logoutRequest);
response.sendRedirect(logoutRequest.getLogoutRequestURI().toString());
} catch (IOException e) {
throw new SSOAgentException(SSOAgentConstants.ErrorMessages.SERVLET_CONNECTION.getMessage(),
SSOAgentConstants.ErrorMessages.SERVLET_CONNECTION.getCode(), e);
}
return logoutRequest.getRequestContext();
}

private boolean handleAuthentication(final HttpServletRequest request, AuthenticationInfo authenticationInfo) {
private boolean handleAuthentication(final HttpServletRequest request, SessionContext authenticationInfo,
Nonce nonce) throws SSOAgentServerException {

AuthorizationResponse authorizationResponse;
AuthorizationCode authorizationCode;
Expand All @@ -173,15 +183,14 @@ private boolean handleAuthentication(final HttpServletRequest request, Authentic
return false;
}

handleSuccessTokenResponse(tokenResponse, authenticationInfo);
handleSuccessTokenResponse(tokenResponse, authenticationInfo, nonce);
return true;
} catch (com.nimbusds.oauth2.sdk.ParseException | SSOAgentServerException | IOException e) {
logger.error(e.getMessage(), e);
return false;
throw new SSOAgentServerException(e.getMessage(), e);
}
}

private void handleSuccessTokenResponse(TokenResponse tokenResponse, AuthenticationInfo authenticationInfo)
private void handleSuccessTokenResponse(TokenResponse tokenResponse, SessionContext sessionContext, Nonce nonce)
throws SSOAgentServerException {

AccessTokenResponse successResponse = tokenResponse.toSuccessResponse();
Expand All @@ -198,13 +207,14 @@ private void handleSuccessTokenResponse(TokenResponse tokenResponse, Authenticat
}

try {
JWTClaimsSet claimsSet = SignedJWT.parse(idToken).getJWTClaimsSet();
User user = new User(claimsSet.getSubject(), getUserAttributes(idToken));

authenticationInfo.setIdToken(JWTParser.parse(idToken));
authenticationInfo.setUser(user);
authenticationInfo.setAccessToken(accessToken);
authenticationInfo.setRefreshToken(refreshToken);
JWT idTokenJWT = JWTParser.parse(idToken);
IDTokenValidator idTokenValidator = new IDTokenValidator(oidcAgentConfig, idTokenJWT);
IDTokenClaimsSet claimsSet = idTokenValidator.validate(nonce);
User user = new User(claimsSet.getSubject().getValue(), getUserAttributes(idToken));
sessionContext.setIdToken(idTokenJWT.getParsedString());
sessionContext.setUser(user);
sessionContext.setAccessToken(accessToken.toJSONString());
sessionContext.setRefreshToken(refreshToken.getValue());
} catch (ParseException e) {
throw new SSOAgentServerException(SSOAgentConstants.ErrorMessages.ID_TOKEN_PARSE.getMessage(),
SSOAgentConstants.ErrorMessages.ID_TOKEN_PARSE.getCode(), e);
Expand Down Expand Up @@ -297,6 +307,11 @@ private void validateForCode(OIDCAgentConfig oidcAgentConfig) throws SSOAgentCli
}

if (oidcAgentConfig.getConsumerKey() == null) {
throw new SSOAgentClientException(SSOAgentConstants.ErrorMessages.AGENT_CONFIG_CLIENT_SECRET.getMessage(),
SSOAgentConstants.ErrorMessages.AGENT_CONFIG_CLIENT_SECRET.getCode());
}

if (oidcAgentConfig.getConsumerSecret() == null) {
throw new SSOAgentClientException(SSOAgentConstants.ErrorMessages.AGENT_CONFIG_CLIENT_ID.getMessage(),
SSOAgentConstants.ErrorMessages.AGENT_CONFIG_CLIENT_ID.getCode());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package io.asgardio.java.oidc.sdk;

import io.asgardio.java.oidc.sdk.config.model.OIDCAgentConfig;
import io.asgardio.java.oidc.sdk.exception.SSOAgentClientException;

/**
* A factory to create Default OIDC Manger objects based on a OIDCAgentConfig.
*/
public class DefaultOIDCManagerFactory {

/**
* Creates a new {@link DefaultOIDCManager} object.
*
* @param oidcAgentConfig The {@link OIDCAgentConfig} object containing the client specific details.
* @return The DefaultOIDCManager instance.
* @throws SSOAgentClientException If the OIDCAgentConfig validation is unsuccessful.
*/
public static OIDCManager createOIDCManager(OIDCAgentConfig oidcAgentConfig) throws SSOAgentClientException {

return new DefaultOIDCManager(oidcAgentConfig);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package io.asgardio.java.oidc.sdk;

import io.asgardio.java.oidc.sdk.bean.RequestContext;
import io.asgardio.java.oidc.sdk.bean.SessionContext;
import io.asgardio.java.oidc.sdk.config.model.OIDCAgentConfig;
import io.asgardio.java.oidc.sdk.exception.SSOAgentClientException;
import io.asgardio.java.oidc.sdk.exception.SSOAgentException;
import io.asgardio.java.oidc.sdk.exception.SSOAgentServerException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
* A wrapper class for the {@link DefaultOIDCManager} that provides
* the functionality defined by the {@link OIDCManager} with using
* HTTP sessions as the storage entity for the {@link RequestContext}
* and {@link SessionContext} information.
*/
public class HTTPSessionBasedOIDCProcessor {

private static final Logger logger = LogManager.getLogger(HTTPSessionBasedOIDCProcessor.class);

private final OIDCManager defaultOIDCManager;

public HTTPSessionBasedOIDCProcessor(OIDCAgentConfig oidcAgentConfig) throws SSOAgentClientException {

defaultOIDCManager = DefaultOIDCManagerFactory.createOIDCManager(oidcAgentConfig);
}

/**
* Builds an authentication request and redirects. Information
* regarding the authentication session would be retrieved via
* {@link RequestContext} object and then, would be written to
* the http session.
*
* @param request Incoming {@link HttpServletRequest}.
* @param response Outgoing {@link HttpServletResponse}.
* @throws SSOAgentException
*/
public void sendForLogin(HttpServletRequest request, HttpServletResponse response)
throws SSOAgentException {

HttpSession session = request.getSession();
RequestContext requestContext = defaultOIDCManager.sendForLogin(request, response);
session.setAttribute(SSOAgentConstants.REQUEST_CONTEXT, requestContext);
}

/**
* Processes the OIDC callback response and extract the authorization
* code, builds a token request, sends the token request and parse
* the token response where the authenticated user info and tokens
* would be added to the {@link SessionContext} object and written
* into the available http session.
*
* @param request Incoming {@link HttpServletRequest}.
* @param response Outgoing {@link HttpServletResponse}.
* @throws SSOAgentException Upon failed authentication.
*/
public void handleOIDCCallback(HttpServletRequest request, HttpServletResponse response) throws SSOAgentException {

RequestContext requestContext = getRequestContext(request);
clearSession(request);
SessionContext sessionContext = defaultOIDCManager.handleOIDCCallback(request, response, requestContext);

if (sessionContext != null) {
clearSession(request);
HttpSession session = request.getSession();
session.setAttribute(SSOAgentConstants.SESSION_CONTEXT, sessionContext);
} else {
throw new SSOAgentServerException("Null session context.");
}
}

/**
* Builds a logout request and redirects.
*
* @param request Incoming {@link HttpServletRequest}.
* @param response Outgoing {@link HttpServletResponse}
* @throws SSOAgentException
*/
public void logout(HttpServletRequest request, HttpServletResponse response) throws SSOAgentException {

SessionContext sessionContext = getSessionContext(request);
clearSession(request);
HttpSession session = request.getSession();
RequestContext requestContext = defaultOIDCManager.logout(sessionContext, response);
session.setAttribute(SSOAgentConstants.REQUEST_CONTEXT, requestContext);
}

private void clearSession(HttpServletRequest request) {

HttpSession session = request.getSession(false);

if (session != null) {
session.invalidate();
}
}

private RequestContext getRequestContext(HttpServletRequest request) throws SSOAgentServerException {

HttpSession session = request.getSession(false);

if (session != null && session.getAttribute(SSOAgentConstants.REQUEST_CONTEXT) != null) {
return (RequestContext) request.getSession(false)
.getAttribute(SSOAgentConstants.REQUEST_CONTEXT);
}
throw new SSOAgentServerException("Request context null.");
}

private SessionContext getSessionContext(HttpServletRequest request) throws SSOAgentServerException {

HttpSession session = request.getSession(false);

if (session != null && session.getAttribute(SSOAgentConstants.SESSION_CONTEXT) != null) {
return (SessionContext) request.getSession(false)
.getAttribute(SSOAgentConstants.SESSION_CONTEXT);
}
throw new SSOAgentServerException("Session context null.");
}
}
Loading

0 comments on commit 52b706f

Please sign in to comment.