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:
parent
d2eb9ab66f
commit
800609161c
10
pom.xml
10
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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<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("&", "&").replaceAll("<", "<")
|
||||
.replaceAll(">", ">");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue