diff --git a/pom.xml b/pom.xml
index dec180c..c25ee04 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,16 @@
custom-portal-handler
provided
+
+ org.gcube.core
+ common-encryption
+ provided
+
+
+ org.gcube.core
+ common-scope-maps
+ compile
+
com.googlecode.json-simple
json-simple
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 1db83f0..edd6592 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
@@ -37,7 +37,8 @@ public class SocialProfile implements EntryPoint {
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."));
+ mainPanel.add(new ErrorAlert("Something went wrong while parsing your professional summary from LinkedIn, please report the issue.", true));
+ displayProfile();
}
else {
mainPanel.clear();
@@ -48,7 +49,8 @@ public class SocialProfile implements EntryPoint {
@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."));
+ mainPanel.add(new ErrorAlert("Something went wrong while communicating with LinkedIn service, please report us the issue.", true));
+ displayProfile();
}
});
@@ -85,14 +87,14 @@ public class SocialProfile implements EntryPoint {
*/
private String checkLinkedInAuthZ() {
if (Window.Location.getParameter("error") != null) {
- mainPanel.add(new ErrorAlert("it seems you denied our request to import your professional summary from LinkedIn."));
+ mainPanel.add(new ErrorAlert("it seems you denied our request to import your professional summary from LinkedIn.", true));
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."));
+ mainPanel.add(new ErrorAlert("Something went wrong when importing your professional summary from LinkedIn, please try again.", true));
return null;
}
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 b25331c..8de2f75 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
@@ -35,6 +35,7 @@ public class DisplayProfile extends Composite {
protected final static String HEADLINE_ERROR = "Your Headline please";
protected final static String ISTI_TEXT = "Company";
protected final static String ISTI_ERROR = "Your Company please";
+ private final static String D4S_APP_ID = "77n7r4c9nwuwk2";
private static DisplayProfileUiBinder uiBinder = GWT
.create(DisplayProfileUiBinder.class);
@@ -135,7 +136,6 @@ public class DisplayProfile extends Composite {
@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
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 3855abe..37331ed 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,9 +1,13 @@
package org.gcube.portlets.user.socialprofile.client.ui;
+import org.gcube.portlets.user.gcubewidgets.client.elements.Span;
+
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
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.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
@@ -16,9 +20,18 @@ public class ErrorAlert extends Composite {
}
@UiField Element errorMessage;
+ @UiField Span handler;
- public ErrorAlert(String message) {
+ public ErrorAlert(String message, boolean removable) {
initWidget(uiBinder.createAndBindUi(this));
errorMessage.setInnerText(message);
+ if (removable)
+ handler.setHTML(" Close");
+ }
+
+
+ @UiHandler("handler")
+ void onCloseClick(ClickEvent e) {
+ this.removeFromParent();
}
}
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 3e42dd9..2654bdd 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
@@ -1,19 +1,32 @@
+ xmlns:g="urn:import:com.google.gwt.user.client.ui"
+ xmlns:m="urn:import:org.gcube.portlets.user.gcubewidgets.client.elements">
.alert {
border: 1px solid #EBCCD1;
- border-radius: 4px;
+ border-radius: 4px;
margin: 20px;
- padding: 15px;
- background-color: #F2DEDE;
- color: #A94440;
- font-family: 'Helvetica Neue', Arial, sans-serif;
- font-size: 14px;
+ padding: 15px;
+ background-color: #F2DEDE;
+ color: #A94440;
+ font-family: 'Helvetica Neue', Arial, sans-serif;
+ font-size: 14px;
+ }
+
+ .profile-link {
+ font-weight: bold;
+ }
+
+ .profile-link:hover {
+ cursor: pointer;
+ cursor: hand;
+ text-decoration: underline;
}
-
- Import Error:
+
+ Import Error:
+
+
\ 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 5b2c77f..e000850 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,5 +1,8 @@
package org.gcube.portlets.user.socialprofile.server;
+import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
+import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
+
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
@@ -23,11 +26,19 @@ 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.common.encryption.StringEncrypter;
+import org.gcube.common.resources.gcore.ServiceEndpoint;
+import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
+import org.gcube.common.resources.gcore.ServiceEndpoint.Property;
+import org.gcube.common.resources.gcore.utils.XPathHelper;
+import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.portal.custom.communitymanager.OrganizationsUtil;
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.gcube.resources.discovery.client.api.DiscoveryClient;
+import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
import org.json.simple.parser.ContainerFactory;
import org.json.simple.parser.JSONParser;
import org.slf4j.Logger;
@@ -48,6 +59,12 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
private static final Logger _log = LoggerFactory.getLogger(SocialServiceImpl.class);
+ private static final String LINKEDIN_HOST_SERVICE_NAME = "host";
+ private static final String LINKEDIN_CLIEND_ID_PROPNAME = "client_id";
+ private static final String LINKEDIN_CLIEND_SECRET_PROPNAME = "client_secret";
+
+ private static final String LINKEDIN_API_REQUEST = "https://api.linkedin.com/v1/people/~:(id,headline,summary,location:(name),industry,positions)";
+
/**
* the current ASLSession
* @return the session
@@ -58,6 +75,7 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
if (user == null) {
_log.warn("USER IS NULL setting test.user and Running OUTSIDE PORTAL");
user = getDevelopmentUser();
+ SessionManager.getInstance().getASLSession(sessionID, user).setScope("/gcube");
}
return SessionManager.getInstance().getASLSession(sessionID, user);
}
@@ -216,32 +234,24 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
}
/**
- * Escape an html string. Escaping data received from the client helps to
- * prevent cross-site script vulnerabilities.
- *
- * @param html the html string to escape
- * @return the escaped string
+ * This method fetches the user profile by authenticating with LinkedIn through OAuth2 protocol
+ * once authenitcated it call another methos to get the profile and parse it.
+ * @return true if everything goes ok, false otherwise.
*/
- private String escapeHtml(String html) {
- if (html == null) {
- return null;
- }
- return html.replaceAll("&", "&").replaceAll("<", "<")
- .replaceAll(">", ">");
- }
@Override
public Boolean fetchUserProfile(String authCode, String redirectURI) {
try {
+ HashMap infoMap = getLinkedInUASInfo();
HttpClient httpClient = HttpClients.createDefault();
- HttpPost httpPost = new HttpPost("https://www.linkedin.com/uas/oauth2/accessToken");
+ HttpPost httpPost = new HttpPost(infoMap.get(LINKEDIN_HOST_SERVICE_NAME));
// 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"));
+ params.add(new BasicNameValuePair("client_id", infoMap.get(LINKEDIN_CLIEND_ID_PROPNAME)));
+ params.add(new BasicNameValuePair("client_secret", infoMap.get(LINKEDIN_CLIEND_SECRET_PROPNAME)));
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
@@ -263,11 +273,9 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
public List creatArrayContainer() {
return new LinkedList();
}
-
public Map createObjectContainer() {
return new LinkedHashMap();
}
-
};
@SuppressWarnings("unchecked")
Map json = (Map) parser.parse(jsonText, containerFactory);
@@ -278,7 +286,7 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
String token = json.get("access_token");
if (token == null)
return false;
-
+ //here we got authorized by the user
return parseProfile(httpClient, token);
}
finally {
@@ -293,16 +301,18 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
return true;
}
-
-
+ /**
+ * Ask the basic profile to LinkedIn API for the authenticated user, parse rge profile and write info into local DB.
+ * @param httpClient
+ * @param token
+ * @return
+ */
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,headline,summary,location:(name),industry)");
-
- // add request header
+ HttpGet request = new HttpGet(LINKEDIN_API_REQUEST);
+ // add request header as in the documentation, @see https://developer.linkedin.com/documents/authentication
request.addHeader("Authorization", "Bearer " + token);
try {
+ _log.debug("Asking LinkedIn profile via http GET for " + getASLSession().getUsername());
HttpResponse httpResponse = httpClient.execute(request);
HttpEntity entity = httpResponse.getEntity();
@@ -316,30 +326,67 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(IOUtils.toInputStream(xmlResponse));
- _log.debug("Parsing xmlResponse ... ");
- doc.getDocumentElement().normalize();
-
+ _log.debug("Parsing LinkedIn profile xmlResponse for " + getASLSession().getUsername());
String headline = "";
- if (doc.getDocumentElement().getElementsByTagName("headline") != null && doc.getDocumentElement().getElementsByTagName("headline").getLength() > 0)
- headline = doc.getDocumentElement().getElementsByTagName("headline").item(0).getTextContent();
-
String summary = "";
- if (doc.getDocumentElement().getElementsByTagName("summary") != null && doc.getDocumentElement().getElementsByTagName("summary").getLength() > 0)
- summary = doc.getDocumentElement().getElementsByTagName("summary").item(0).getTextContent();
-
String location = "";
- if (doc.getDocumentElement().getElementsByTagName("name") != null && doc.getDocumentElement().getElementsByTagName("name").getLength() > 0)
- location = doc.getDocumentElement().getElementsByTagName("name").item(0).getTextContent();
-
String industry = "";
- if (doc.getDocumentElement().getElementsByTagName("industry") != null && doc.getDocumentElement().getElementsByTagName("industry").getLength() > 0)
- industry = doc.getDocumentElement().getElementsByTagName("industry").item(0).getTextContent();
- System.out.println("headline: " + headline);
- System.out.println("summary: " + summary);
- System.out.println("location: " + location);
- System.out.println("industry: " + industry);
-
+ List currValue = null;
+ XPathHelper helper = new XPathHelper(doc.getDocumentElement());
+ currValue = helper.evaluate("/person/headline/text()");
+ if (currValue != null && currValue.size() > 0) {
+ headline = currValue.get(0);
+ }
+ currValue = helper.evaluate("/person/summary/text()");
+ if (currValue != null && currValue.size() > 0) {
+ summary = currValue.get(0);
+ }
+ currValue = helper.evaluate("/person/location/name/text()");
+ if (currValue != null && currValue.size() > 0) {
+ location = currValue.get(0);
+ }
+ currValue = helper.evaluate("/person/industry/text()");
+ if (currValue != null && currValue.size() > 0) {
+ industry = currValue.get(0);
+ }
+ //positions
+ String positions = "";
+ currValue = helper.evaluate("/person/positions/position");
+ int positionsNo = currValue.size();
+ if (positionsNo > 0) {
+ positions = (positionsNo > 1) ? "\n\nCurrent Positions:" : "\n\nCurrent Position:";
+ for (int i = 0; i < positionsNo; i++) {
+ currValue = helper.evaluate("/person/positions/position/title/text()");
+ if (currValue != null && currValue.size() > 0) {
+ positions += "\n\n"+currValue.get(i);
+ }
+ currValue = helper.evaluate("/person/positions/position/company/name/text()");
+ if (currValue != null && currValue.size() > 0) {
+ positions += "\n"+currValue.get(i);
+ }
+ currValue = helper.evaluate("/person/positions/position/company/type/text()");
+ if (currValue != null && currValue.size() > 0) {
+ positions += "\n" + currValue.get(i)+"; ";
+ }
+ currValue = helper.evaluate("/person/positions/position/company/size/text()");
+ if (currValue != null && currValue.size() > 0) {
+ positions += currValue.get(i)+"; ";
+ }
+ currValue = helper.evaluate("/person/positions/position/company/industry/text()");
+ if (currValue != null && currValue.size() > 0) {
+ positions += currValue.get(i) + " industry.";
+ }
+ currValue = helper.evaluate("/person/positions/position/summary/text()");
+ if (currValue != null && currValue.size() > 0) {
+ positions += "\n\n" + currValue.get(i);
+ }
+ }
+ }
+ //add the positions to the summary
+ summary += positions;
+ _log.debug("LinkedIn Profile gotten correctly for " + getASLSession().getUsername() + " attempting to write into DB ...");
+
if (isWithinPortal()) {
com.liferay.portal.model.User user;
user = UserLocalServiceUtil.getUserByScreenName(OrganizationsUtil.getCompany().getCompanyId(), getASLSession().getUsername());
@@ -354,12 +401,11 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
//summary
if (summary.compareTo("") != 0)
user.setComments(escapeHtml(summary));
-
+
return (UserLocalServiceUtil.updateUser(user) != null);
} else {
_log.warn("Development Mode ON, not attempting to write into DB");
}
-
}
finally {
myInputStream.close();
@@ -372,4 +418,66 @@ public class SocialServiceImpl extends RemoteServiceServlet implements SocialSer
}
}
+
+ /**
+ * this method return the URL and the keys to access the LinkedIn User AuthN Service
+ * @return an hashmap containing the 3 info needed
+ */
+ private HashMap getLinkedInUASInfo(){
+ String scope = getASLSession().getScope();
+ _log.info("Looking for a LinkedIn UAS in " + scope);
+ String previousScope = ScopeProvider.instance.get();
+ ScopeProvider.instance.set(scope);
+ SimpleQuery query = queryFor(ServiceEndpoint.class);
+ query.addCondition("$resource/Profile/Category/string() eq 'Service'");
+ query.addCondition("$resource/Profile/Name/string() eq 'LinkedIn-user-authorization'");
+
+ DiscoveryClient client = clientFor(ServiceEndpoint.class);
+ List list = client.submit(query);
+ ScopeProvider.instance.set(previousScope);
+
+ if (list.size() > 1) {
+ _log.warn("Multiple LinkedIn-user-authorization Service Endpoints available in the scope, should be only one.");
+ return null;
+ }
+ else if (list.size() == 1) {
+ ServiceEndpoint se = list.get(0);
+ AccessPoint ap = se.profile().accessPoints().iterator().next();
+ String authServiceURL = ap.address();
+ String clientId = "";
+ String clientSecret = "";
+ try {
+ for (Property property : ap.properties()) {
+ if (property.name().compareTo(LINKEDIN_CLIEND_ID_PROPNAME) == 0)
+ clientId = StringEncrypter.getEncrypter().decrypt(property.value());
+ if (property.name().compareTo(LINKEDIN_CLIEND_SECRET_PROPNAME) == 0)
+ clientSecret = StringEncrypter.getEncrypter().decrypt(property.value());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ HashMap map = new HashMap();
+ map.put(LINKEDIN_HOST_SERVICE_NAME, authServiceURL);
+ map.put(LINKEDIN_CLIEND_ID_PROPNAME, clientId);
+ map.put(LINKEDIN_CLIEND_SECRET_PROPNAME, clientSecret);
+ return map;
+ }
+ else return null;
+ }
+
+ /**
+ * Escape an html string. Escaping data received from the client helps to
+ * prevent cross-site script vulnerabilities.
+ *
+ * @param html the html string to escape
+ * @return the escaped string
+ */
+ private String escapeHtml(String html) {
+ if (html == null) {
+ return null;
+ }
+ return html.replaceAll("&", "&").replaceAll("<", "<")
+ .replaceAll(">", ">");
+ }
}