Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

add refresh token logic - direct challange service #310

Merged
merged 2 commits into from
Aug 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions conf/web/WEB-INF/applicationContext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1555,12 +1555,16 @@
<bean id="myCreatedChallengesAction"
class="com.topcoder.direct.services.view.action.my.MyCreatedChallengesAction" scope="prototype">
<property name="serviceURL" value="@directChallengeServicesApiUrl@"/>
<property name="ssoLoginUrl" value="@ssoLoginUrl@"/>
<property name="authorizationURL" value="@authorizationUrl@"/>
<property name="userService" ref="userService"/>
</bean>

<bean id="myChallengesAction"
class="com.topcoder.direct.services.view.action.my.MyChallengesAction" scope="prototype">
<property name="serviceURL" value="@directChallengeServicesApiUrl@"/>
<property name="ssoLoginUrl" value="@ssoLoginUrl@"/>
<property name="authorizationURL" value="@authorizationUrl@"/>
</bean>

<bean id="xmlPhaseTemplatePersistence"
Expand Down
6 changes: 4 additions & 2 deletions conf/web/WEB-INF/struts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1613,14 +1613,16 @@

<package name="my" namespace="/my" extends="base">
<action name="createdChallenges" class="myCreatedChallengesAction">
<result>/WEB-INF/my/myCreatedChallenges.jsp</result>
<result name="success">/WEB-INF/my/myCreatedChallenges.jsp</result>
<result name="forward" type="redirect">${ssoLoginUrl}</result>
</action>
<action name="getCreatedChallenges" method="getMyCreatedChallenges" class="myCreatedChallengesAction">
<result name="success" type="json"/>
<result name="error" type="json"/>
</action>
<action name="challenges" class="myChallengesAction">
<result>/WEB-INF/my/myChallenges.jsp</result>
<result name="success">/WEB-INF/my/myChallenges.jsp</result>
<result name="forward" type="redirect">${ssoLoginUrl}</result>
</action>
<action name="getMyChallenges" method="getMyChallenges" class="myChallengesAction">
<result name="success" type="json"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
/*
* Copyright (C) 2014 TopCoder Inc., All Rights Reserved.
* Copyright (C) 2014 - 2017 TopCoder Inc., All Rights Reserved.
*/
package com.topcoder.direct.services.view.action;

import com.topcoder.direct.services.configs.ServerConfiguration;
import com.topcoder.direct.services.view.dto.contest.ContestStatus;
import com.topcoder.direct.services.view.dto.my.SingleRestResult;
import com.topcoder.direct.services.view.dto.my.Token;
import com.topcoder.direct.services.view.dto.project.ProjectBriefDTO;
import com.topcoder.direct.services.view.exception.JwtAuthenticationException;
import com.topcoder.direct.services.view.util.DataProvider;
import com.topcoder.direct.services.view.util.DirectUtils;
import com.topcoder.security.TCSubject;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.log4j.Logger;
import org.apache.struts2.ServletActionContext;
Expand All @@ -27,11 +33,9 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

/**
* <p>
Expand Down Expand Up @@ -104,6 +108,11 @@ public abstract class ServiceBackendDataTablesAction extends AbstractAction {
*/
private String serviceURL;

/**
* authorization Url
*/
private String authorizationURL;

/**
* The challenge types options in filter panel
*
Expand All @@ -120,7 +129,6 @@ public abstract class ServiceBackendDataTablesAction extends AbstractAction {

/**
* The customer options in filter panel.
*
* @since 1.1
*/
private Map<Long, String> customers;
Expand Down Expand Up @@ -188,6 +196,11 @@ public abstract class ServiceBackendDataTablesAction extends AbstractAction {
*/
private String endDateTo;

/**
* ssoLogin Url
*/
private String ssoLoginUrl;

/**
* The max pagination size.
*/
Expand All @@ -213,6 +226,11 @@ public abstract class ServiceBackendDataTablesAction extends AbstractAction {
*/
protected static final String ERROR_MESSAGE_FORMAT = "Service URL:%s, HTTP Status Code:%d, Error Message:%s";

/**
* URI params for refresh token
*/
private final String AUTHORIZATION_PARAMS = "{\"param\": {\"externalToken\": \"%s\"}}";

/**
* The jackson object mapping which is used to deserialize json return from API to domain model.
*/
Expand Down Expand Up @@ -321,23 +339,26 @@ protected JsonNode getJsonResultFromAPI(URI apiEndPoint) throws Exception {
// specify the get request
HttpGet getRequest = new HttpGet(apiEndPoint);

Cookie jwtCookie = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
Cookie jwtCookieV3 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
ServerConfiguration.JWT_V3_COOKIE_KEY);
Cookie jwtCookieV2 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
ServerConfiguration.JWT_COOOKIE_KEY);

if (jwtCookie == null) {
throw new Exception("The jwt cookie for the authorized user could not be loaded");
if (jwtCookieV2 == null) {
throw new JwtAuthenticationException("Please re-login");
}

validateCookieV2V3(jwtCookieV2,jwtCookieV3);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deedee should we get the cookies again?

Copy link
Contributor Author

@deedee deedee Aug 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean why it check twice?I think we need to check there since that method called by some action directly i.e /my/getMyCreatedChallenges. If it is valid will not be refreshed


getRequest.setHeader(HttpHeaders.AUTHORIZATION,
"Bearer " + jwtCookie.getValue());
"Bearer " + jwtCookieV3.getValue());

getRequest.addHeader(HttpHeaders.ACCEPT, "application/json");

HttpResponse httpResponse = httpClient.execute(getRequest);
HttpEntity entity = httpResponse.getEntity();

if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {

throw new Exception(String.format(ERROR_MESSAGE_FORMAT,
getRequest.getURI(),
httpResponse.getStatusLine().getStatusCode(),
Expand Down Expand Up @@ -706,4 +727,144 @@ public String getEndDateTo() {
public void setEndDateTo(String endDateTo) {
this.endDateTo = endDateTo;
}

/**
* Getter for {@link #authorizationURL}
* @return authorizationURL
*/
public String getAuthorizationURL() {
return authorizationURL;
}

/**
* Setter for {@link #authorizationURL}
* @param authorizationURL
*/
public void setAuthorizationURL(String authorizationURL) {
this.authorizationURL = authorizationURL;
}

/**
* Get Full SSO login url
* @return
*/
public String getSsoLoginUrl() {
try {
URIBuilder builder = new URIBuilder(ssoLoginUrl);
builder.addParameter("next", ServletActionContext.getRequest().getRequestURL().toString());
return builder.build().toString();
} catch (Exception e) {
return ssoLoginUrl;
}
}

/**
* Setter {@link #ssoLoginUrl}
*
* @param ssoLoginUrl
*/
public void setSsoLoginUrl(String ssoLoginUrl) {
this.ssoLoginUrl = ssoLoginUrl;
}

/**
* Refresh token from API endpoint
*
* @param oldToken
* @return
* @throws Exception
*/
private Token getRefreshTokenFromApi(String oldToken) throws Exception{
DefaultHttpClient httpClient = new DefaultHttpClient();
SingleRestResult<Token> resultToken = null;
try {
URI authorizationUri = new URI(getAuthorizationURL());
HttpPost httpPost = new HttpPost(authorizationUri);
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, "application/json");

StringEntity body = new StringEntity(String.format(AUTHORIZATION_PARAMS, oldToken));
httpPost.setEntity(body);
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new JwtAuthenticationException(String.format(ERROR_MESSAGE_FORMAT, authorizationUri,
response.getStatusLine().getStatusCode(),
getErrorMessage(response.getStatusLine().getStatusCode())));
}

JsonNode result = objectMapper.readTree(entity.getContent());
resultToken = objectMapper.readValue(result.get("result"),
objectMapper.getTypeFactory().constructParametricType(SingleRestResult.class, Token.class));
} finally {
httpClient.getConnectionManager().shutdown();
}
return resultToken.getContent();
}

/**
* Verify token.If token expired: refresh it
*
* @param tokenV3
* @param tokenV2
* @return
* @throws JwtAuthenticationException
*/
private String getValidJwtToken(String tokenV3, String tokenV2) throws JwtAuthenticationException {
String[] tokenSplit = tokenV3.split("\\.");
boolean valid = true;
if (tokenSplit.length < 2) valid = false;

JsonNode jsonNode = null;

try {
if (valid) {
StringBuffer payloadStr = new StringBuffer(tokenSplit[1]);
while (payloadStr.length() % 4 != 0) payloadStr.append('=');
String payload = new String(Base64.decodeBase64(payloadStr.toString().getBytes(StandardCharsets.UTF_8)));

jsonNode = objectMapper.readValue(payload.toString(), JsonNode.class);

long exp = jsonNode.get("exp").getLongValue();
Date expDate = new Date(exp * 1000);
logger.info("token expire: " + expDate);
if (expDate.before(new Date())) valid = false;
}

if (!valid) {
logger.info("refresh new token for : " + tokenV2);
Token newToken = getRefreshTokenFromApi(tokenV2);
if (newToken == null || newToken.getToken().isEmpty()) {
throw new JwtAuthenticationException("Invalid refresh token");
}

return newToken.getToken();
}
} catch (Exception e) {
throw new JwtAuthenticationException("Failed to refresh toke through api, Please go to sso login page : " +
getSsoLoginUrl());
}
return tokenV3;
}

/**
* Validate cookie v2 and v3
*
* @param v2 cookie v2
* @param v3 cookie v3
* @throws Exception
*/
protected void validateCookieV2V3(Cookie v2, Cookie v3) throws Exception{
String validToken = null;
String v3Token = null;
if (v3 == null) {
validToken = getRefreshTokenFromApi(v2.getValue()).getToken();
} else {
validToken = getValidJwtToken(v3.getValue(), v2.getValue());
v3Token = v3.getValue();
}

if (!validToken.equals(v3Token)) {
DirectUtils.addDirectCookie(ServletActionContext.getResponse(), ServerConfiguration.JWT_V3_COOKIE_KEY, validToken, -1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.topcoder.direct.services.view.action.ServiceBackendDataTablesAction;
import com.topcoder.direct.services.view.dto.my.Challenge;
import com.topcoder.direct.services.view.dto.my.RestResult;
import com.topcoder.direct.services.view.exception.JwtAuthenticationException;
import com.topcoder.direct.services.view.util.DirectUtils;
import org.apache.struts2.ServletActionContext;
import org.codehaus.jackson.JsonNode;
Expand Down Expand Up @@ -54,12 +55,22 @@ public class MyChallengesAction extends ServiceBackendDataTablesAction {
*/
@Override
public String execute() throws Exception {
Cookie jwtCookie = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
Cookie jwtCookieV3 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
ServerConfiguration.JWT_V3_COOKIE_KEY);

Cookie jwtCookieV2 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
ServerConfiguration.JWT_COOOKIE_KEY);

if (jwtCookie == null) {
if (jwtCookieV2 == null) {
return ANONYMOUS;
}

try {
validateCookieV2V3(jwtCookieV2,jwtCookieV3);
} catch (JwtAuthenticationException e) {
return "forward";
}

// populate filter data
this.setupFilterPanel();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import com.topcoder.direct.services.view.action.ServiceBackendDataTablesAction;
import com.topcoder.direct.services.view.dto.my.Challenge;
import com.topcoder.direct.services.view.dto.my.RestResult;
import com.topcoder.direct.services.view.exception.JwtAuthenticationException;
import com.topcoder.direct.services.view.util.DirectUtils;
import com.topcoder.service.user.UserService;
import org.apache.struts2.ServletActionContext;
import org.codehaus.jackson.JsonNode;

import javax.servlet.http.Cookie;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
Expand Down Expand Up @@ -62,13 +62,22 @@ public class MyCreatedChallengesAction extends ServiceBackendDataTablesAction {
*/
@Override
public String execute() throws Exception {
Cookie jwtCookie = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
Cookie jwtCookieV3 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
ServerConfiguration.JWT_V3_COOKIE_KEY);

Cookie jwtCookieV2 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
ServerConfiguration.JWT_COOOKIE_KEY);

if (jwtCookie == null) {
if (jwtCookieV2 == null) {
return ANONYMOUS;
}

try {
validateCookieV2V3(jwtCookieV2,jwtCookieV3);
} catch (JwtAuthenticationException e) {
return "forward";
}

// populate filter data
this.setupFilterPanel();

Expand Down
Loading