ready to release

git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/user/social-profile@99511 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Massimiliano Assante 2014-09-04 16:08:31 +00:00
parent d2eb9ab66f
commit 800609161c
6 changed files with 207 additions and 61 deletions

10
pom.xml
View File

@ -66,6 +66,16 @@
<artifactId>custom-portal-handler</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gcube.core</groupId>
<artifactId>common-encryption</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gcube.core</groupId>
<artifactId>common-scope-maps</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -1,19 +1,32 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:m="urn:import:org.gcube.portlets.user.gcubewidgets.client.elements">
<ui:style>
.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;
}
</ui:style>
<g:HTMLPanel styleName="{style.alert}">
<span style="font-weight: bold;"> Import Error: </span><span ui:field="errorMessage"></span>
<g:HTMLPanel styleName="{style.alert}">
<span style="font-weight: bold;"> Import Error: </span>
<span ui:field="errorMessage"></span>
<m:Span ui:field="handler" styleName="{style.profile-link}"></m:Span>
</g:HTMLPanel>
</ui:UiBinder>

View File

@ -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("&", "&amp;").replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
}
@Override
public Boolean fetchUserProfile(String authCode, String redirectURI) {
try {
HashMap<String, String> 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<NameValuePair> params = new ArrayList<NameValuePair>(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<String, String> json = (Map<String, String>) 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<String> 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<String, String> 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<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
List<ServiceEndpoint> 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<String, String> map = new HashMap<String, String>();
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("&", "&amp;").replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
}
}