package org.gcube.portlets.user.shareupdates.server; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.UUID; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.commons.validator.routines.UrlValidator; import org.gcube.application.framework.core.session.ASLSession; import org.gcube.application.framework.core.session.SessionManager; import org.gcube.common.core.utils.logging.GCUBEClientLog; import org.gcube.portal.custom.communitymanager.OrganizationsUtil; import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper; import org.gcube.portal.databook.server.DBCassandraAstyanaxImpl; import org.gcube.portal.databook.server.DatabookStore; import org.gcube.portal.databook.shared.ClientFeed; import org.gcube.portal.databook.shared.Feed; import org.gcube.portal.databook.shared.FeedType; import org.gcube.portal.databook.shared.PrivacyLevel; import org.gcube.portal.databook.shared.UserInfo; import org.gcube.portal.databook.shared.ex.FeedIDNotFoundException; import org.gcube.portlets.user.homelibrary.home.HomeLibrary; import org.gcube.portlets.user.homelibrary.home.User; import org.gcube.portlets.user.homelibrary.home.exceptions.HomeNotFoundException; import org.gcube.portlets.user.homelibrary.home.exceptions.InternalErrorException; import org.gcube.portlets.user.homelibrary.home.workspace.Workspace; import org.gcube.portlets.user.homelibrary.home.workspace.exceptions.WorkspaceFolderNotFoundException; import org.gcube.portlets.user.pickuser.shared.PickingUser; import org.gcube.portlets.user.shareupdates.client.ShareUpdateService; import org.gcube.portlets.user.shareupdates.server.metaseeker.MetaSeeker; import org.gcube.portlets.user.shareupdates.server.opengraph.OpenGraph; import org.gcube.portlets.user.shareupdates.shared.LinkPreview; import org.gcube.portlets.user.shareupdates.shared.UserSettings; import org.gcube.vomanagement.usermanagement.GroupManager; import org.gcube.vomanagement.usermanagement.UserManager; import org.gcube.vomanagement.usermanagement.exception.UserManagementPortalException; import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayGroupManager; import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayUserManager; import org.gcube.vomanagement.usermanagement.model.GroupModel; import org.gcube.vomanagement.usermanagement.model.UserModel; import org.htmlparser.beans.StringBean; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.tidy.Tidy; 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.Organization; import com.liferay.portal.model.Role; import com.liferay.portal.service.OrganizationLocalServiceUtil; import com.liferay.portal.service.UserLocalServiceUtil; import com.liferay.portal.theme.ThemeDisplay; import com.sun.net.ssl.HttpsURLConnection; /** * The server side implementation of the RPC service. */ @SuppressWarnings("serial") public class ShareUpdateServiceImpl extends RemoteServiceServlet implements ShareUpdateService { /** * */ private static final String ADMIN_ROLE = "Administrator"; /** * */ private static GCUBEClientLog _log = new GCUBEClientLog(ShareUpdateServiceImpl.class); /** * The store interface */ private DatabookStore store; /** * used for debugging in eclipse */ private boolean withinPortal = false; /** * connect to cassandra at startup */ public void init() { store = new DBCassandraAstyanaxImpl(); } public void destroy() { store.closeConnection(); } /** * the current ASLSession * @return the session */ private ASLSession getASLSession() { String sessionID = this.getThreadLocalRequest().getSession().getId(); String user = (String) this.getThreadLocalRequest().getSession().getAttribute(ScopeHelper.USERNAME_ATTRIBUTE); if (user == null) { _log.warn("USER IS NULL setting test.user and Running OUTSIDE PORTAL"); // user = "test.user"; user = "massimiliano.assante"; SessionManager.getInstance().getASLSession(sessionID, user).setScope("/gcube/devsec/devVRE"); withinPortal = false; } else { withinPortal = true; } System.out.println("SessionID = " + sessionID); return SessionManager.getInstance().getASLSession(sessionID, user); } /** * */ public ClientFeed share(String feedText, FeedType feedType, PrivacyLevel pLevel, String vreId, String linkTitle, String linkDesc, String url, String urlThumbnail, String host) { String escapedFeedText = escapeHtml(feedText); String username = getASLSession().getUsername(); String email = username+"@isti.cnr.it"; String fullName = username+" FULL"; String thumbnailURL = "images/Avatar_default.png"; if (withinPortal) { try { UserInfo user = getUserSettings().getUserInfo(); email = user.getEmailaddress(); fullName = user.getFullName(); thumbnailURL = user.getAvatarId(); } catch (Exception e) { e.printStackTrace(); } } Date feedDate = new Date(); Feed toShare = new Feed(UUID.randomUUID().toString(), feedType, username, feedDate, "", url, urlThumbnail, transformUrls(escapedFeedText), pLevel, fullName, email, thumbnailURL, linkTitle, linkDesc, host); _log.trace("Attempting to save Feed with text: " + escapedFeedText + " Level: " + pLevel); boolean result = store.saveUserFeed(toShare); //need to put the feed into VRES Timeline too if (pLevel == PrivacyLevel.VRES) { _log.trace("PrivacyLevel was set to VRES attempting to write onto User's VRES Timelines"); for (GroupModel vre : getUserVREs(username)) { String vreScope = getScopeByOrganizationId(vre.getGroupId()); _log.trace("Attempting to write onto " + vreScope); try { store.saveFeedToVRETimeline(toShare.getKey(), vreScope); } catch (FeedIDNotFoundException e) { _log.error("Error writing onto VRES Time Line" + vreScope); } //save the feed _log.trace("Success writing onto " + vreScope); } } //share on a single VRE Timeline //receives a VreId(groupId) get the scope from the groupId else if (pLevel == PrivacyLevel.SINGLE_VRE && vreId != null && vreId.compareTo("") != 0) { String vreScope = getScopeByOrganizationId(vreId); _log.trace("Attempting to write onto " + vreScope); try { store.saveFeedToVRETimeline(toShare.getKey(), vreScope); } catch (FeedIDNotFoundException e) { _log.error("Error writing onto VRES Time Line" + vreScope); } //save the feed _log.trace("Success writing onto " + vreScope); } if (!result) return null; ClientFeed cf = new ClientFeed(toShare.getKey(), toShare.getType().toString(), username, feedDate, toShare.getUri(), replaceAmpersand(toShare.getDescription()), fullName, email, thumbnailURL, toShare.getLinkTitle(), toShare.getLinkDescription(), toShare.getUriThumbnail(), toShare.getLinkHost()); return cf; } private UserSettings getUserSettingsFromSession() { return (UserSettings) getASLSession().getAttribute(UserInfo.USER_INFO_ATTR); } private void setUserSettingsInSession(UserSettings user) { getASLSession().setAttribute(UserInfo.USER_INFO_ATTR, user); } private String replaceAmpersand(String toReplace) { String toReturn = toReplace.replaceAll("&", "&"); return toReturn; } /** * utilty method that convert a url ina text in a clickable url by the browser * and if the user has just pasted a link, converts the link in: shared a link * @param feedText * @return the text with the clickable url in it */ public String transformUrls(String feedText) { StringBuilder sb = new StringBuilder(); // separate input by spaces ( URLs have no spaces ) String [] parts = feedText.split("\\s"); // Attempt to convert each item into an URL. for (int i = 0; i < parts.length; i++) { if (parts[i].startsWith("http")) { try { URL url = new URL(parts[i]); if (i == 0 && parts.length == 1) //then he shared just a link return sb.append("shared ").append("a link.").append(" ").toString(); // If possible then replace with anchor... sb.append("").append(url).append(" "); } catch (MalformedURLException e) { // If there was an URL then it's not valid _log.error("MalformedURLException returning... "); return feedText; } } else { sb.append(parts[i]); sb.append(" "); } } return sb.toString(); } public UserSettings getUserSettings() { if (getUserSettingsFromSession() != null) return getUserSettingsFromSession(); try { ASLSession session = getASLSession(); String username = session.getUsername(); String email = username+"@isti.cnr.it"; String fullName = username+" FULL"; String thumbnailURL = "images/Avatar_default.png"; if (withinPortal) { getUserVREs(username); com.liferay.portal.model.UserModel user = UserLocalServiceUtil.getUserByScreenName(OrganizationsUtil.getCompany().getCompanyId(), username); thumbnailURL = "/image/user_male_portrait?img_id="+user.getPortraitId(); fullName = user.getFirstName() + " " + user.getLastName(); email = user.getEmailAddress(); ThemeDisplay themeDisplay = (ThemeDisplay) this.getThreadLocalRequest().getSession().getAttribute(WebKeys.THEME_DISPLAY); String accountURL = themeDisplay.getURLMyAccount().toString(); HashMap vreNames = getUserVreNames(username); UserInfo userInfo = new UserInfo(username, fullName, thumbnailURL, user.getEmailAddress(), accountURL, true, isAdmin(), vreNames); UserSettings toReturn = new UserSettings(userInfo, 0, session.getScopeName(), isInfrastructureScope()); setUserSettingsInSession(toReturn); return toReturn; } else { _log.info("Returning test USER"); HashMap fakeVreNames = new HashMap(); fakeVreNames.put("/gcube/devsec/devVRE","devVRE"); //fakeVreNames.put("/gcube/devNext/NexNext","NexNext"); UserInfo user = new UserInfo(getASLSession().getUsername(), fullName, thumbnailURL, email, "fakeAccountUrl", true, false, fakeVreNames); return new UserSettings(user, 0, session.getScopeName(), isInfrastructureScope()); } } catch (Exception e) { e.printStackTrace(); } return new UserSettings(); } /** * return the id as key and the names as value of the vre a user is subscribed to * @param username * @return the id as key and the names as value of the vre a user is subscribed to */ private HashMap getUserVreNames(String username) { HashMap toReturn = new HashMap(); if (isInfrastructureScope()) { for (GroupModel vre : getUserVREs(username)) { toReturn.put(vre.getGroupId(), vre.getGroupName()); } } else { for (GroupModel vre : getUserVREs(username)) { if (vre.getGroupName().compareTo(getASLSession().getGroupName())==0) toReturn.put(vre.getGroupId(), vre.getGroupName()); } } return toReturn; } /** * tell if the user is a portal administrator or not * @param username * @return true if is admin * @throws SystemException * @throws PortalException */ private boolean isAdmin() throws PortalException, SystemException { com.liferay.portal.model.User currUser = OrganizationsUtil.validateUser(getASLSession().getUsername()); List organizations = OrganizationLocalServiceUtil.getOrganizations(0, OrganizationLocalServiceUtil.getOrganizationsCount()); Organization rootOrganization = null; for (Organization organization : organizations) { if (organization.getName().equals(OrganizationsUtil.getRootOrganizationName() ) ) { rootOrganization = organization; break; } } try { _log.trace("root: " + rootOrganization.getName() ); return (hasRole(ADMIN_ROLE, rootOrganization.getName(), currUser)); } catch (NullPointerException e) { _log.error("Cannot find root organziation, please check gcube-data.properties file in $CATALINA_HOME/conf folder"); return false; } } /** * * @param rolename * @param organizationName * @param user * @return * @throws SystemException */ private boolean hasRole(String rolename, String organizationName, com.liferay.portal.model.User user) throws SystemException { for (Role role : user.getRoles()) if (role.getName().compareTo(rolename) == 0 ) return true; return false; } /** * * @param username * @return */ private ArrayList getUserVREs(String username) { ArrayList toReturn = new ArrayList(); com.liferay.portal.model.User currUser; try { GroupManager gm = new LiferayGroupManager(); currUser = OrganizationsUtil.validateUser(username); for (Organization org : currUser.getOrganizations()) if (gm.isVRE(org.getOrganizationId()+"")) { GroupModel toAdd = gm.getGroup(""+org.getOrganizationId()); toReturn.add(toAdd); } } catch (Exception e) { _log.error("Failed reading User VREs for : " + username); e.printStackTrace(); return toReturn; } return toReturn; } private String getScopeByOrganizationId(String vreid) { GroupManager gm = new LiferayGroupManager(); try { return gm.getScope(vreid); } catch (Exception e) { _log.error("Could not find a scope for this VREid: " + vreid); 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(">", ">"); } /** * utilty method that extract an url ina text * @param feedText * @return the text with the clickable url in it */ public String extractURL(String feedText) { // separate input by spaces ( URLs have no spaces ) String [] parts = feedText.split("\\s"); // Attempt to convert each item into an URL. for( String item : parts ) { if (item.startsWith("http")) { try { new URL(item); return item; } catch (MalformedURLException e) { // If there was an URL then it's not valid _log.error("MalformedURLException returning... "); return null; } } } return null; } /** * tries the following in the indicated order for Populating the Link preview * Open Graph protocol * Meta "title" and "description" tags * Best guess from page content (not recommended) * * Schema.org microdata <-- This is still a TODO */ public LinkPreview checkLink(String linkToCheck) { LinkPreview toReturn = null; _log.info("to check " + linkToCheck); //look for a url in text linkToCheck = extractURL(linkToCheck); if (linkToCheck == null) return null; //no url String[] schemes = {"http","https"}; UrlValidator urlValidator = new UrlValidator(schemes); if (! urlValidator.isValid(linkToCheck)) { _log.warn("url is NOT valid, returning nothing"); return null; } _log.debug("url is valid"); URL pageURL; URLConnection siteConnection = null; try { pageURL = new URL(linkToCheck); if (pageURL.getProtocol().equalsIgnoreCase("https")) { System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol"); java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); trustAllHTTPSConnections(); siteConnection = (HttpsURLConnection) pageURL.openConnection(); } else siteConnection = (HttpURLConnection) pageURL.openConnection(); } catch (MalformedURLException e) { _log.error("url is not valid"); return null; } catch (IOException e) { _log.error("url is not reachable"); return null; } String title; String description; ArrayList imageUrls = new ArrayList(); //get the host from the url String host = pageURL.getHost().replaceAll("www.", ""); //try openGraph First OpenGraph ogLink = null; try { ogLink = new OpenGraph(linkToCheck, true, siteConnection); if (ogLink == null || ogLink.getContent("title") == null) { //there is no OpenGraph for this link _log.info("No OpenGraph Found, going Best guess from page content") ; toReturn = getInfoFromHTML(pageURL, linkToCheck, host); } else { //there is OpenGraph title = ogLink.getContent("title"); description = (ogLink.getContent("description") != null) ? ogLink.getContent("description") : ""; description = ((description.length() > 256) ? description.substring(0, 256)+"..." : description); //look for the imagem ask the guesser if not present if (ogLink.getContent("image") != null) imageUrls.add(ogLink.getContent("image")); else { ArrayList images = getImagesFromHTML(pageURL); if (! images.isEmpty()) imageUrls = images; } toReturn = new LinkPreview(title, description, linkToCheck, host, imageUrls); return toReturn; } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return toReturn; } /** * get all the image urls from an HTML page up to 15 * @param pageURL the url * @return a list of image url * @throws IOException */ private ArrayList getImagesFromHTML(URL pageURL) throws IOException { ArrayList toReturn = new ArrayList(); InputStream input = pageURL.openStream(); try { Document document = new Tidy().parseDOM(input, null); NodeList imgs = document.getElementsByTagName("img"); int upTo = (imgs.getLength() > 15) ? 15 : imgs.getLength(); for (int i = 0; i < upTo; i++) { System.out.println(i); toReturn.add(imgs.item(i).getAttributes().getNamedItem("src").getNodeValue()); } }catch (NullPointerException e) { _log.error("Error parsing HTML for images, malformed HTML returning what I found so far ... "); return toReturn; } return toReturn; } /** * to use when OpenGraph is not available, Tries Metadata first, then Best guess from page content * @param pageUrl * @param link * @param host * @return a LinPreview object instance filled with the extracted information * @throws IOException */ private LinkPreview getInfoFromHTML(URL pageUrl, String link, String host) throws Exception { LinkPreview toReturn = null; String title = ""; String description = ""; InputStream input = pageUrl.openStream(); Document document = new Tidy().parseDOM(input, null); NodeList titles = document.getElementsByTagName("title"); if (titles != null && titles.getLength()>0) { if (titles.item(0).getFirstChild() == null || titles.item(0).getFirstChild().getNodeValue() == null) { _log.error("[MANUAL-PARSE] Something wrong with the title element, returning ... "); return toReturn; } title = titles.item(0).getFirstChild().getNodeValue(); MetaSeeker ms = null; try { ms = new MetaSeeker(link); } catch(Exception e) { _log.error("[MANUAL-PARSE] Something wrong with the meta seeker returning ... "); return toReturn; } //try the metadata, otherwise ask the guesser description = (ms.getContent("description") != null && ! ms.getContent("description").isEmpty()) ? ms.getContent("description") : createDescriptionFromContent(link); ArrayList images = new ArrayList(); NodeList imgs = document.getElementsByTagName("img"); int upTo = (imgs.getLength() > 15) ? 15 : imgs.getLength(); for (int i = 0; i < upTo; i++) { String imageUrl = imgs.item(i).getAttributes().getNamedItem("src").getNodeValue(); if (imageUrl.startsWith("/")) imageUrl = pageUrl.getProtocol()+"://"+pageUrl.getHost()+imageUrl; images.add(imageUrl); } toReturn = new LinkPreview(title, description, link, host, images); } return toReturn; } /** * generate the description parsing the content (Best Guess) * @param link the link to check * @return the description guessed */ private String createDescriptionFromContent(String link) { StringBean sb = new StringBean(); sb.setURL(link); sb.setLinks(false); String description = sb.getStrings(); description = ((description.length() > 256) ? description.substring(0, 256)+"..." : description); return description; } /** * this method handles the non trusted https connections */ private void trustAllHTTPSConnections() { // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType) { } public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType) { } } }; try { SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } catch (Exception e) { System.out.println("Error" + e); } } /** * Indicates whether the scope is the whole infrastructure. * @return true if it is, false otherwise. */ private boolean isInfrastructureScope() { return getASLSession().getScope().isInfrastructure(); } @Override public ArrayList getPortalUsers() { ArrayList portalUsers = new ArrayList(); try { if (withinPortal) { UserManager um = new LiferayUserManager(); Workspace workspace = getWorkspace(); List users = workspace.getHome().getHomeManager().getUsers(); for (User user : users) { _log.trace("Trying to get additional info for "+user.getPortalLogin()); if (user.getPortalLogin().compareTo("test.user") != 0) { //skip test.user UserModel curr = null; String thumbnailURL = ""; try { curr = um.getUserByScreenName(user.getPortalLogin()); com.liferay.portal.model.UserModel lifeUser = UserLocalServiceUtil.getUserByScreenName(OrganizationsUtil.getCompany().getCompanyId(), user.getPortalLogin()); thumbnailURL = "/image/user_male_portrait?img_id="+lifeUser.getPortraitId(); } catch (UserManagementPortalException ume) { _log.error("Error while getUserByScreenName for " + user.getPortalLogin()); portalUsers.add(new PickingUser(user.getId(), user.getPortalLogin(), user.getPortalLogin(), "unknown image")); } if (curr != null) portalUsers.add(new PickingUser(user.getId(), curr.getScreenName(), curr.getFullname(), thumbnailURL)); } } } else { //test users portalUsers.add(new PickingUser("11111", "massimiliano.assante", "Massimiliano Assante", "image")); portalUsers.add(new PickingUser("11111", "massimiliano.assante", "Massimiliano Pazzante", "image")); portalUsers.add(new PickingUser("11111", "massimiliano.assante", "Massimiliano Ruzzante", "image")); portalUsers.add(new PickingUser("11111", "massimiliano.assante", "Massimiliano Cantante", "image")); portalUsers.add(new PickingUser("11111", "massimiliano.assante", "Massimiliano Ballerino", "image")); portalUsers.add(new PickingUser("11111", "massimiliano.assante", "Massimiliano Violante", "image")); portalUsers.add(new PickingUser("11111", "giogio.giorgi", "Giorgio Giorgietti", "image")); portalUsers.add(new PickingUser("2222", "pino.pinetti", "Pino Dall'ara", "image")); portalUsers.add(new PickingUser("11333111", "rino.gattuso", "Rino Gattuso", "image")); portalUsers.add(new PickingUser("114444111", "alex.delpiero", "Alessandro Del Piero", "image")); portalUsers.add(new PickingUser("3462", "sandro.nesta", "Alessandro Nesta", "image")); portalUsers.add(new PickingUser("11464321", "samsung.mobile", "Samsung Mobile", "image")); } } catch (Exception e) { _log.error("Error in server get all contacts ", e); } return portalUsers; } /** * * @return the workspace instance * @throws InternalErrorException * @throws HomeNotFoundException * @throws WorkspaceFolderNotFoundException */ private Workspace getWorkspace() throws InternalErrorException, HomeNotFoundException, WorkspaceFolderNotFoundException { final ASLSession session = getASLSession(); Workspace workspace = HomeLibrary.getUserWorkspace(session); return workspace; } }