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

Commit 7e32bb4

Browse files
authored
Merge pull request #312 from appirio-tech/dev
challenge api switch, scorecard list fix, member lookup fix
2 parents 7736dac + 52e00a7 commit 7e32bb4

17 files changed

+386
-66
lines changed

conf/ApplicationServer.properties

+1
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ SSO_HASH_SECRET = @ApplicationServer.SSO_HASH_SECRET@
4646
SSO_DOMAIN = @ApplicationServer.SSO_DOMAIN@
4747

4848
JWT_COOKIE_KEY = @ApplicationServer.JWT_COOKIE_KEY@
49+
JWT_V3_COOKIE_KEY = @ApplicationServer.JWT_V3_COOKIE_KEY@
4950

5051
DIRECT_API_SERVICE_ENDPOINT = @ApplicationServer.DIRECT_API_SERVICE_ENDPOINT@

conf/web/WEB-INF/applicationContext.xml

+6-2
Original file line numberDiff line numberDiff line change
@@ -1554,13 +1554,17 @@
15541554

15551555
<bean id="myCreatedChallengesAction"
15561556
class="com.topcoder.direct.services.view.action.my.MyCreatedChallengesAction" scope="prototype">
1557-
<property name="serviceURL" value="/challenges"/>
1557+
<property name="serviceURL" value="@directChallengeServicesApiUrl@"/>
1558+
<property name="ssoLoginUrl" value="@ssoLoginUrl@"/>
1559+
<property name="authorizationURL" value="@authorizationUrl@"/>
15581560
<property name="userService" ref="userService"/>
15591561
</bean>
15601562

15611563
<bean id="myChallengesAction"
15621564
class="com.topcoder.direct.services.view.action.my.MyChallengesAction" scope="prototype">
1563-
<property name="serviceURL" value="/challenges"/>
1565+
<property name="serviceURL" value="@directChallengeServicesApiUrl@"/>
1566+
<property name="ssoLoginUrl" value="@ssoLoginUrl@"/>
1567+
<property name="authorizationURL" value="@authorizationUrl@"/>
15641568
</bean>
15651569

15661570
<bean id="xmlPhaseTemplatePersistence"

conf/web/WEB-INF/struts.xml

+4-2
Original file line numberDiff line numberDiff line change
@@ -1613,14 +1613,16 @@
16131613

16141614
<package name="my" namespace="/my" extends="base">
16151615
<action name="createdChallenges" class="myCreatedChallengesAction">
1616-
<result>/WEB-INF/my/myCreatedChallenges.jsp</result>
1616+
<result name="success">/WEB-INF/my/myCreatedChallenges.jsp</result>
1617+
<result name="forward" type="redirect">${ssoLoginUrl}</result>
16171618
</action>
16181619
<action name="getCreatedChallenges" method="getMyCreatedChallenges" class="myCreatedChallengesAction">
16191620
<result name="success" type="json"/>
16201621
<result name="error" type="json"/>
16211622
</action>
16221623
<action name="challenges" class="myChallengesAction">
1623-
<result>/WEB-INF/my/myChallenges.jsp</result>
1624+
<result name="success">/WEB-INF/my/myChallenges.jsp</result>
1625+
<result name="forward" type="redirect">${ssoLoginUrl}</result>
16241626
</action>
16251627
<action name="getMyChallenges" method="getMyChallenges" class="myChallengesAction">
16261628
<result name="success" type="json"/>

src/java/main/com/topcoder/direct/services/configs/ServerConfiguration.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ public class ServerConfiguration extends ApplicationServer {
5555
*
5656
* @since 1.1
5757
*/
58-
public static String JWT_COOOKIE_KEY = bundle.getProperty("JWT_COOOKIE_KEY", "tcjwt");
58+
public static String JWT_COOOKIE_KEY = bundle.getProperty("JWT_COOKIE_KEY", "tcjwt");
5959

6060
/**
6161
* The end point URL of the direct API. The default value is set to empty
6262
*
6363
* @since 1.1
6464
*/
6565
public static String DIRECT_API_SERVICE_ENDPOINT = bundle.getProperty("DIRECT_API_SERVICE_ENDPOINT", "");
66+
67+
public static String JWT_V3_COOKIE_KEY = bundle.getProperty("JWT_V3_COOKIE_KEY", "v3jwt");
6668
}

src/java/main/com/topcoder/direct/services/view/action/ServiceBackendDataTablesAction.java

+186-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
/*
2-
* Copyright (C) 2014 TopCoder Inc., All Rights Reserved.
2+
* Copyright (C) 2014 - 2017 TopCoder Inc., All Rights Reserved.
33
*/
44
package com.topcoder.direct.services.view.action;
55

66
import com.topcoder.direct.services.configs.ServerConfiguration;
77
import com.topcoder.direct.services.view.dto.contest.ContestStatus;
8+
import com.topcoder.direct.services.view.dto.my.SingleRestResult;
9+
import com.topcoder.direct.services.view.dto.my.Token;
810
import com.topcoder.direct.services.view.dto.project.ProjectBriefDTO;
11+
import com.topcoder.direct.services.view.exception.JwtAuthenticationException;
912
import com.topcoder.direct.services.view.util.DataProvider;
1013
import com.topcoder.direct.services.view.util.DirectUtils;
1114
import com.topcoder.security.TCSubject;
15+
import org.apache.commons.codec.binary.Base64;
1216
import org.apache.http.HttpEntity;
1317
import org.apache.http.HttpHeaders;
1418
import org.apache.http.HttpResponse;
1519
import org.apache.http.HttpStatus;
1620
import org.apache.http.client.methods.HttpGet;
21+
import org.apache.http.client.methods.HttpPost;
1722
import org.apache.http.client.utils.URIBuilder;
23+
import org.apache.http.entity.StringEntity;
1824
import org.apache.http.impl.client.DefaultHttpClient;
1925
import org.apache.log4j.Logger;
2026
import org.apache.struts2.ServletActionContext;
@@ -23,13 +29,13 @@
2329
import org.codehaus.jackson.map.ObjectMapper;
2430

2531
import javax.servlet.http.Cookie;
32+
import java.io.UnsupportedEncodingException;
2633
import java.net.URI;
2734
import java.net.URISyntaxException;
35+
import java.net.URLEncoder;
36+
import java.nio.charset.StandardCharsets;
2837
import java.text.SimpleDateFormat;
29-
import java.util.ArrayList;
30-
import java.util.LinkedHashMap;
31-
import java.util.List;
32-
import java.util.Map;
38+
import java.util.*;
3339

3440
/**
3541
* <p>
@@ -102,6 +108,11 @@ public abstract class ServiceBackendDataTablesAction extends AbstractAction {
102108
*/
103109
private String serviceURL;
104110

111+
/**
112+
* authorization Url
113+
*/
114+
private String authorizationURL;
115+
105116
/**
106117
* The challenge types options in filter panel
107118
*
@@ -118,7 +129,6 @@ public abstract class ServiceBackendDataTablesAction extends AbstractAction {
118129

119130
/**
120131
* The customer options in filter panel.
121-
*
122132
* @since 1.1
123133
*/
124134
private Map<Long, String> customers;
@@ -186,6 +196,11 @@ public abstract class ServiceBackendDataTablesAction extends AbstractAction {
186196
*/
187197
private String endDateTo;
188198

199+
/**
200+
* ssoLogin Url
201+
*/
202+
private String ssoLoginUrl;
203+
189204
/**
190205
* The max pagination size.
191206
*/
@@ -211,6 +226,11 @@ public abstract class ServiceBackendDataTablesAction extends AbstractAction {
211226
*/
212227
protected static final String ERROR_MESSAGE_FORMAT = "Service URL:%s, HTTP Status Code:%d, Error Message:%s";
213228

229+
/**
230+
* URI params for refresh token
231+
*/
232+
private final String AUTHORIZATION_PARAMS = "{\"param\": {\"externalToken\": \"%s\"}}";
233+
214234
/**
215235
* The jackson object mapping which is used to deserialize json return from API to domain model.
216236
*/
@@ -265,23 +285,29 @@ protected void setupFilterPanel() throws Exception {
265285
* @return the built URI.
266286
* @throws URISyntaxException if the syntax is error.
267287
*/
268-
protected URI buildServiceEndPoint(Map<String, String> parameters) throws URISyntaxException {
288+
protected URI buildServiceEndPoint(Map<String, String> parameters) throws URISyntaxException, UnsupportedEncodingException {
269289

270290
int pageSize = getiDisplayLength() == -1 ? MAX_PAGINATION_SIZE : getiDisplayLength();
271291

272292

273-
URIBuilder builder = new URIBuilder();
274-
builder.setScheme("http").setHost(ServerConfiguration.DIRECT_API_SERVICE_ENDPOINT).setPath(getServiceURL())
275-
.setParameter("offset", String.valueOf(getiDisplayStart()))
293+
URIBuilder builder = new URIBuilder(getServiceURL());
294+
295+
builder.setParameter("offset", String.valueOf(getiDisplayStart()))
276296
.setParameter("limit", String.valueOf(pageSize));
277297

298+
String filters = "";
278299
if (parameters != null) {
279300
for (String key : parameters.keySet()) {
280-
builder.setParameter(key, parameters.get(key));
301+
if ("filter".equals(key)) {
302+
filters = URLEncoder.encode(parameters.get(key), "UTF-8");
303+
} else {
304+
builder.setParameter(key, parameters.get(key));
305+
}
281306
}
282307
}
283308

284-
return builder.build();
309+
return new URI(new StringBuilder(builder.build().toString()).append("&filter=").append(filters).toString());
310+
285311
}
286312

287313
/**
@@ -313,23 +339,26 @@ protected JsonNode getJsonResultFromAPI(URI apiEndPoint) throws Exception {
313339
// specify the get request
314340
HttpGet getRequest = new HttpGet(apiEndPoint);
315341

316-
Cookie jwtCookie = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
342+
Cookie jwtCookieV3 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
343+
ServerConfiguration.JWT_V3_COOKIE_KEY);
344+
Cookie jwtCookieV2 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
317345
ServerConfiguration.JWT_COOOKIE_KEY);
318346

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

351+
validateCookieV2V3(jwtCookieV2,jwtCookieV3);
352+
323353
getRequest.setHeader(HttpHeaders.AUTHORIZATION,
324-
"Bearer " + jwtCookie.getValue());
354+
"Bearer " + jwtCookieV3.getValue());
325355

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

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

331361
if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
332-
333362
throw new Exception(String.format(ERROR_MESSAGE_FORMAT,
334363
getRequest.getURI(),
335364
httpResponse.getStatusLine().getStatusCode(),
@@ -698,4 +727,144 @@ public String getEndDateTo() {
698727
public void setEndDateTo(String endDateTo) {
699728
this.endDateTo = endDateTo;
700729
}
730+
731+
/**
732+
* Getter for {@link #authorizationURL}
733+
* @return authorizationURL
734+
*/
735+
public String getAuthorizationURL() {
736+
return authorizationURL;
737+
}
738+
739+
/**
740+
* Setter for {@link #authorizationURL}
741+
* @param authorizationURL
742+
*/
743+
public void setAuthorizationURL(String authorizationURL) {
744+
this.authorizationURL = authorizationURL;
745+
}
746+
747+
/**
748+
* Get Full SSO login url
749+
* @return
750+
*/
751+
public String getSsoLoginUrl() {
752+
try {
753+
URIBuilder builder = new URIBuilder(ssoLoginUrl);
754+
builder.addParameter("next", ServletActionContext.getRequest().getRequestURL().toString());
755+
return builder.build().toString();
756+
} catch (Exception e) {
757+
return ssoLoginUrl;
758+
}
759+
}
760+
761+
/**
762+
* Setter {@link #ssoLoginUrl}
763+
*
764+
* @param ssoLoginUrl
765+
*/
766+
public void setSsoLoginUrl(String ssoLoginUrl) {
767+
this.ssoLoginUrl = ssoLoginUrl;
768+
}
769+
770+
/**
771+
* Refresh token from API endpoint
772+
*
773+
* @param oldToken
774+
* @return
775+
* @throws Exception
776+
*/
777+
private Token getRefreshTokenFromApi(String oldToken) throws Exception{
778+
DefaultHttpClient httpClient = new DefaultHttpClient();
779+
SingleRestResult<Token> resultToken = null;
780+
try {
781+
URI authorizationUri = new URI(getAuthorizationURL());
782+
HttpPost httpPost = new HttpPost(authorizationUri);
783+
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, "application/json");
784+
785+
StringEntity body = new StringEntity(String.format(AUTHORIZATION_PARAMS, oldToken));
786+
httpPost.setEntity(body);
787+
HttpResponse response = httpClient.execute(httpPost);
788+
HttpEntity entity = response.getEntity();
789+
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
790+
throw new JwtAuthenticationException(String.format(ERROR_MESSAGE_FORMAT, authorizationUri,
791+
response.getStatusLine().getStatusCode(),
792+
getErrorMessage(response.getStatusLine().getStatusCode())));
793+
}
794+
795+
JsonNode result = objectMapper.readTree(entity.getContent());
796+
resultToken = objectMapper.readValue(result.get("result"),
797+
objectMapper.getTypeFactory().constructParametricType(SingleRestResult.class, Token.class));
798+
} finally {
799+
httpClient.getConnectionManager().shutdown();
800+
}
801+
return resultToken.getContent();
802+
}
803+
804+
/**
805+
* Verify token.If token expired: refresh it
806+
*
807+
* @param tokenV3
808+
* @param tokenV2
809+
* @return
810+
* @throws JwtAuthenticationException
811+
*/
812+
private String getValidJwtToken(String tokenV3, String tokenV2) throws JwtAuthenticationException {
813+
String[] tokenSplit = tokenV3.split("\\.");
814+
boolean valid = true;
815+
if (tokenSplit.length < 2) valid = false;
816+
817+
JsonNode jsonNode = null;
818+
819+
try {
820+
if (valid) {
821+
StringBuffer payloadStr = new StringBuffer(tokenSplit[1]);
822+
while (payloadStr.length() % 4 != 0) payloadStr.append('=');
823+
String payload = new String(Base64.decodeBase64(payloadStr.toString().getBytes(StandardCharsets.UTF_8)));
824+
825+
jsonNode = objectMapper.readValue(payload.toString(), JsonNode.class);
826+
827+
long exp = jsonNode.get("exp").getLongValue();
828+
Date expDate = new Date(exp * 1000);
829+
logger.info("token expire: " + expDate);
830+
if (expDate.before(new Date())) valid = false;
831+
}
832+
833+
if (!valid) {
834+
logger.info("refresh new token for : " + tokenV2);
835+
Token newToken = getRefreshTokenFromApi(tokenV2);
836+
if (newToken == null || newToken.getToken().isEmpty()) {
837+
throw new JwtAuthenticationException("Invalid refresh token");
838+
}
839+
840+
return newToken.getToken();
841+
}
842+
} catch (Exception e) {
843+
throw new JwtAuthenticationException("Failed to refresh toke through api, Please go to sso login page : " +
844+
getSsoLoginUrl());
845+
}
846+
return tokenV3;
847+
}
848+
849+
/**
850+
* Validate cookie v2 and v3
851+
*
852+
* @param v2 cookie v2
853+
* @param v3 cookie v3
854+
* @throws Exception
855+
*/
856+
protected void validateCookieV2V3(Cookie v2, Cookie v3) throws Exception{
857+
String validToken = null;
858+
String v3Token = null;
859+
if (v3 == null) {
860+
validToken = getRefreshTokenFromApi(v2.getValue()).getToken();
861+
} else {
862+
validToken = getValidJwtToken(v3.getValue(), v2.getValue());
863+
v3Token = v3.getValue();
864+
}
865+
866+
if (!validToken.equals(v3Token)) {
867+
DirectUtils.addDirectCookie(ServletActionContext.getResponse(), ServerConfiguration.JWT_V3_COOKIE_KEY, validToken, -1);
868+
}
869+
}
701870
}

src/java/main/com/topcoder/direct/services/view/action/my/MyChallengesAction.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.topcoder.direct.services.view.action.ServiceBackendDataTablesAction;
88
import com.topcoder.direct.services.view.dto.my.Challenge;
99
import com.topcoder.direct.services.view.dto.my.RestResult;
10+
import com.topcoder.direct.services.view.exception.JwtAuthenticationException;
1011
import com.topcoder.direct.services.view.util.DirectUtils;
1112
import org.apache.struts2.ServletActionContext;
1213
import org.codehaus.jackson.JsonNode;
@@ -54,12 +55,22 @@ public class MyChallengesAction extends ServiceBackendDataTablesAction {
5455
*/
5556
@Override
5657
public String execute() throws Exception {
57-
Cookie jwtCookie = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
58+
Cookie jwtCookieV3 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
59+
ServerConfiguration.JWT_V3_COOKIE_KEY);
60+
61+
Cookie jwtCookieV2 = DirectUtils.getCookieFromRequest(ServletActionContext.getRequest(),
5862
ServerConfiguration.JWT_COOOKIE_KEY);
5963

60-
if (jwtCookie == null) {
64+
if (jwtCookieV2 == null) {
6165
return ANONYMOUS;
6266
}
67+
68+
try {
69+
validateCookieV2V3(jwtCookieV2,jwtCookieV3);
70+
} catch (JwtAuthenticationException e) {
71+
return "forward";
72+
}
73+
6374
// populate filter data
6475
this.setupFilterPanel();
6576

0 commit comments

Comments
 (0)