diff --git a/pom.xml b/pom.xml index 53b785f..dec180c 100644 --- a/pom.xml +++ b/pom.xml @@ -66,6 +66,10 @@ custom-portal-handler provided + + com.googlecode.json-simple + json-simple + org.gcube.applicationsupportlayer aslcore @@ -93,6 +97,11 @@ [2.13.0-SNAPSHOT, 3.0.0-SNAPSHOT) compile + + org.apache.httpcomponents + httpclient + 4.3.5 + com.liferay.portal portal-service diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialProfile.java b/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialProfile.java index ed6625b..15dbd6f 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialProfile.java +++ b/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialProfile.java @@ -10,8 +10,10 @@ import org.gcube.portlets.user.socialprofile.shared.UserContext; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.VerticalPanel; @@ -19,16 +21,45 @@ import com.google.gwt.user.client.ui.VerticalPanel; * Entry point classes define onModuleLoad(). */ public class SocialProfile implements EntryPoint { - + private final SocialServiceAsync socialService = GWT.create(SocialService.class); - + private VerticalPanel mainPanel = new VerticalPanel(); private DisplayProfile dispProfile = new DisplayProfile(); - + public void onModuleLoad() { if (isUserAuthZFromLinkedIn()) { - checkLinkedInAuthZ(); + String authorizationCode = checkLinkedInAuthZ(); + if (authorizationCode != null) { + mainPanel.add(new OkAlert("Authorization OK! Please wait while we import from LinkedIn ... ")); + socialService.fetchUserProfile(authorizationCode, DisplayProfile.getRedirectURI(), new AsyncCallback() { + @Override + public void onSuccess(Boolean result) { + if (!result) { + mainPanel.clear(); + mainPanel.add(new ErrorAlert("Something went wrong while parsing your professional summary from LinkedIn, please report the issue.")); + } + else + Window.alert("funzia!"); + } + @Override + public void onFailure(Throwable caught) { + mainPanel.clear(); + mainPanel.add(new ErrorAlert("Something went wrong while communicating with LinkedIn service, please report us the issue.")); + } + }); + + } + } else { + displayProfile(); } + + RootPanel.get("SocialProfileDiv").add(mainPanel); + } + /** + * display the profile of the user + */ + private void displayProfile() { socialService.getUserContext(getUserToShowId(), new AsyncCallback() { @Override public void onSuccess(UserContext result) { @@ -44,21 +75,27 @@ public class SocialProfile implements EntryPoint { dispProfile.showError(caught.getMessage()); } }); - - - RootPanel.get("SocialProfileDiv").add(mainPanel); } - - private void checkLinkedInAuthZ() { + /** + * + * @return the token if everything goers ok, null otherwise + */ + private String checkLinkedInAuthZ() { if (Window.Location.getParameter("error") != null) { - mainPanel.add(new ErrorAlert()); - } else { - String code = Window.Location.getParameter("code"); - String controlSequence = Window.Location.getParameter("state"); - GWT.log("key="+code+" state="+controlSequence); - GWT.log("state="+controlSequence); - mainPanel.add(new OkAlert()); + mainPanel.add(new ErrorAlert("it seems you denied our request to import your professional summary from LinkedIn.")); + return null; + } + String code = Window.Location.getParameter("code"); + String controlSequence = Window.Location.getParameter("state"); + String cSeq2Compare = Cookies.getCookie(DisplayProfile.CONTROL_SEQUENCE_COOKIE); + if (controlSequence.compareTo(cSeq2Compare) != 0) { + mainPanel.add(new ErrorAlert("Something went wrong when importing your professional summary from LinkedIn, please try again.")); + return null; } + + GWT.log("key="+code+" state="+controlSequence); + GWT.log("state="+controlSequence); + return code; } /** @@ -74,7 +111,7 @@ public class SocialProfile implements EntryPoint { } /** * - * @return true if either the user + * @return true if the user has clicked import from LinkedIn */ private boolean isUserAuthZFromLinkedIn() { if (Window.Location.getParameter("error") != null || Window.Location.getParameter("code") != null) diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialService.java b/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialService.java index cad7057..e691341 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialService.java +++ b/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialService.java @@ -15,4 +15,6 @@ public interface SocialService extends RemoteService { Boolean saveHeadline(String newHeadline); Boolean saveIsti(String institution); + + Boolean fetchUserProfile(String authCode, String redirectURI); } diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialServiceAsync.java b/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialServiceAsync.java index 2137292..589cf54 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialServiceAsync.java +++ b/src/main/java/org/gcube/portlets/user/socialprofile/client/SocialServiceAsync.java @@ -11,5 +11,7 @@ public interface SocialServiceAsync { void saveHeadline(String newHeadline, AsyncCallback callback); void saveIsti(String institution, AsyncCallback callback); + + void fetchUserProfile(String authCode, String redirectURI, AsyncCallback callback); } diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/DisplayProfile.java b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/DisplayProfile.java index 112d37c..b25331c 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/DisplayProfile.java +++ b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/DisplayProfile.java @@ -17,6 +17,7 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.Random; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.Location; @@ -44,8 +45,11 @@ public class DisplayProfile extends Composite { public static final String loading = GWT.getModuleBaseURL() + "../images/avatarLoader.gif"; public static final String savingImage = GWT.getModuleBaseURL() + "../images/saving.gif"; public static final String GET_OID_PARAMETER = "oid"; + public static final String CONTROL_SEQUENCE_COOKIE = "CSRF-check-d4science"; + private final SocialServiceAsync socialService = GWT.create(SocialService.class); + @UiField HTMLPanel mainPanel; @UiField Image avatarImage; @UiField HTML userFullName; @@ -127,15 +131,20 @@ public class DisplayProfile extends Composite { importButton.setVisible(true); importButton.addClickHandler(new ClickHandler() { + //TODO: make it a runtime resource @Override public void onClick(ClickEvent event) { String OAUTH2_SERVICE = "https://www.linkedin.com/uas/oauth2/authorization?response_type=code"; String D4S_APP_ID = "77n7r4c9nwuwk2"; String controlSequence = getRandomString(); + + //needed to prevent Cross Site Request Forgery attacks + Cookies.setCookie(CONTROL_SEQUENCE_COOKIE, controlSequence); + String url = OAUTH2_SERVICE + "" + "&client_id="+D4S_APP_ID + "&state="+controlSequence - + "&redirect_uri="+Location.getHref(); + + "&redirect_uri="+getRedirectURI(); Location.assign(url); } @@ -152,7 +161,18 @@ public class DisplayProfile extends Composite { messageButton.setVisible(true); } } - + /** + * + * @return the redirect uri when authorized (or not) by LinkedIn via oAuth2 + */ + public static String getRedirectURI() { + String redirectURI = Window.Location.getProtocol()+"//"+Window.Location.getHost()+Window.Location.getPath(); + //development case + if (Window.Location.getParameter("gwt.codesvr") != null) + return redirectURI+"?gwt.codesvr=127.0.0.1:9997"; + return redirectURI; + } + @UiHandler("editHeadline") void onEditHeadlineClick(ClickEvent e) { headlineLabel.setVisible(false); @@ -309,9 +329,7 @@ public class DisplayProfile extends Composite { } return html.replaceAll("&", "&").replaceAll("<", "<") .replaceAll(">", ">"); - } - - + } private String getRandomString() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/ErrorAlert.java b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/ErrorAlert.java index bd793e5..3855abe 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/ErrorAlert.java +++ b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/ErrorAlert.java @@ -1,7 +1,9 @@ package org.gcube.portlets.user.socialprofile.client.ui; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; @@ -12,10 +14,11 @@ public class ErrorAlert extends Composite { interface ErrorAlertUiBinder extends UiBinder { } - public ErrorAlert() { - initWidget(uiBinder.createAndBindUi(this)); - } - public ErrorAlert(String firstName) { + + @UiField Element errorMessage; + + public ErrorAlert(String message) { initWidget(uiBinder.createAndBindUi(this)); + errorMessage.setInnerText(message); } } diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/ErrorAlert.ui.xml b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/ErrorAlert.ui.xml index b3d8256..3e42dd9 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/ErrorAlert.ui.xml +++ b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/ErrorAlert.ui.xml @@ -14,6 +14,6 @@ } - Import Error: it seems you denied our request to import from LinkedIn. + Import Error: \ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/OkAlert.java b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/OkAlert.java index 05e6e98..74b7b4c 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/OkAlert.java +++ b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/OkAlert.java @@ -1,7 +1,9 @@ package org.gcube.portlets.user.socialprofile.client.ui; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; @@ -11,9 +13,10 @@ public class OkAlert extends Composite { interface OkAlertUiBinder extends UiBinder { } - - public OkAlert() { + @UiField Element message; + public OkAlert(String message2Show) { initWidget(uiBinder.createAndBindUi(this)); + message.setInnerText(message2Show); } } diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/OkAlert.ui.xml b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/OkAlert.ui.xml index 71100dd..9a175a8 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/OkAlert.ui.xml +++ b/src/main/java/org/gcube/portlets/user/socialprofile/client/ui/OkAlert.ui.xml @@ -14,6 +14,6 @@ } - Import operation completed successfully. + \ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/socialprofile/server/SocialServiceImpl.java b/src/main/java/org/gcube/portlets/user/socialprofile/server/SocialServiceImpl.java index 977d499..7291e36 100644 --- a/src/main/java/org/gcube/portlets/user/socialprofile/server/SocialServiceImpl.java +++ b/src/main/java/org/gcube/portlets/user/socialprofile/server/SocialServiceImpl.java @@ -1,7 +1,23 @@ package org.gcube.portlets.user.socialprofile.server; +import java.io.InputStream; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; import org.gcube.application.framework.core.session.ASLSession; import org.gcube.application.framework.core.session.SessionManager; import org.gcube.portal.custom.communitymanager.OrganizationsUtil; @@ -9,12 +25,12 @@ import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper; import org.gcube.portal.databook.shared.UserInfo; import org.gcube.portlets.user.socialprofile.client.SocialService; import org.gcube.portlets.user.socialprofile.shared.UserContext; +import org.json.simple.parser.ContainerFactory; +import org.json.simple.parser.JSONParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gwt.user.server.rpc.RemoteServiceServlet; -import com.liferay.portal.kernel.exception.PortalException; -import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.kernel.util.WebKeys; import com.liferay.portal.model.User; import com.liferay.portal.service.UserLocalServiceUtil; @@ -204,6 +220,98 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer return html.replaceAll("&", "&").replaceAll("<", "<") .replaceAll(">", ">"); } + @Override + public Boolean fetchUserProfile(String authCode, String redirectURI) { + try { + HttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost("https://www.linkedin.com/uas/oauth2/accessToken"); + // Request parameters and other properties. + ArrayList params = new ArrayList(5); + params.add(new BasicNameValuePair("grant_type", "authorization_code")); + params.add(new BasicNameValuePair("code", authCode)); + params.add(new BasicNameValuePair("redirect_uri", redirectURI)); + params.add(new BasicNameValuePair("client_id", "xxx")); + params.add(new BasicNameValuePair("client_secret", "xxx")); + + httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); + + //Execute and get the response. + HttpResponse httpResponse = httpClient.execute(httpPost); + HttpEntity entity = httpResponse.getEntity(); + + if (entity != null) { + InputStream myInputStream = entity.getContent(); + try { + String jsonText = IOUtils.toString(myInputStream, "UTF-8"); + _log.debug("LinkedIn response: " + jsonText); + if (jsonText == null) + return false; + + JSONParser parser = new JSONParser(); + @SuppressWarnings("rawtypes") + ContainerFactory containerFactory = new ContainerFactory(){ + public List creatArrayContainer() { + return new LinkedList(); + } + + public Map createObjectContainer() { + return new LinkedHashMap(); + } + + }; + @SuppressWarnings("unchecked") + Map json = (Map) parser.parse(jsonText, containerFactory); + + if (json.get("error") != null) + return false; + + String token = json.get("access_token"); + if (token == null) + return false; + + return parseProfile(httpClient, token); + } + finally { + myInputStream.close(); + } + } + + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + return true; + } + + + private boolean parseProfile(HttpClient httpClient, String token) { + // HttpGet request = new HttpGet("https://api.linkedin.com/v1/people/~"); + + HttpGet request = new HttpGet("https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,summary,location:(name),industry)"); + + // add request header + request.addHeader("Authorization", "Bearer " + token); + try { + HttpResponse httpResponse = httpClient.execute(request); + HttpEntity entity = httpResponse.getEntity(); + + if (entity != null) { + InputStream myInputStream = entity.getContent(); + try { + String xmlResponse = IOUtils.toString(myInputStream, "UTF-8"); + _log.debug("LinkedIn xmlResponse: " + xmlResponse); + } + finally { + myInputStream.close(); + } + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } } diff --git a/src/main/resources/clientlog4j.properties b/src/main/resources/clientlog4j.properties new file mode 100644 index 0000000..fd367a5 --- /dev/null +++ b/src/main/resources/clientlog4j.properties @@ -0,0 +1,18 @@ +log4j.rootLogger=DEBUG, A1 +log4j.appender.A1=org.apache.log4j.ConsoleAppender +log4j.appender.A1.layout=org.apache.log4j.PatternLayout + +# Print the date in ISO 8601 format +log4j.appender.A1.layout.ConversionPattern=%d %-5p %c - %m%n + +# Print only messages of level TRACE or above in the package org.gcube +log4j.logger.org.gcube=TRACE +log4j.logger.org.gcube.application.framework.core.session=INFO +log4j.logger.org.gcube.contentmanager=ERROR +log4j.logger.org.gcube.common.scope=ERROR +log4j.logger.org.gcube.contentmanagement=ERROR +log4j.logger.org.gcube.resources.discovery.icclient=ERROR +log4j.logger.org.gcube.common.clients=ERROR +log4j.logger.org.gcube.common.homelibrary.jcr=ERROR +log4j.logger.org.gcube.application.framework.accesslogger=ERROR +log4j.logger.org.apache.pdfbox.util.PDFStreamEngine=ERROR