11/*
2- * Copyright (C) 2014 TopCoder Inc., All Rights Reserved.
2+ * Copyright (C) 2014 - 2017 TopCoder Inc., All Rights Reserved.
33 */
44package com .topcoder .direct .services .view .action ;
55
66import com .topcoder .direct .services .configs .ServerConfiguration ;
77import 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 ;
810import com .topcoder .direct .services .view .dto .project .ProjectBriefDTO ;
11+ import com .topcoder .direct .services .view .exception .JwtAuthenticationException ;
912import com .topcoder .direct .services .view .util .DataProvider ;
1013import com .topcoder .direct .services .view .util .DirectUtils ;
1114import com .topcoder .security .TCSubject ;
15+ import org .apache .commons .codec .binary .Base64 ;
1216import org .apache .http .HttpEntity ;
1317import org .apache .http .HttpHeaders ;
1418import org .apache .http .HttpResponse ;
1519import org .apache .http .HttpStatus ;
1620import org .apache .http .client .methods .HttpGet ;
21+ import org .apache .http .client .methods .HttpPost ;
1722import org .apache .http .client .utils .URIBuilder ;
23+ import org .apache .http .entity .StringEntity ;
1824import org .apache .http .impl .client .DefaultHttpClient ;
1925import org .apache .log4j .Logger ;
2026import org .apache .struts2 .ServletActionContext ;
2329import org .codehaus .jackson .map .ObjectMapper ;
2430
2531import javax .servlet .http .Cookie ;
32+ import java .io .UnsupportedEncodingException ;
2633import java .net .URI ;
2734import java .net .URISyntaxException ;
35+ import java .net .URLEncoder ;
36+ import java .nio .charset .StandardCharsets ;
2837import 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}
0 commit comments