| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  | package controllers;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import com.fasterxml.jackson.databind.JsonNode;
 | 
					
						
							|  |  |  | import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
					
						
							|  |  |  | import com.typesafe.config.Config;
 | 
					
						
							|  |  |  | import org.apache.commons.lang3.StringUtils;
 | 
					
						
							|  |  |  | import play.Logger;
 | 
					
						
							|  |  |  | import play.Play;
 | 
					
						
							|  |  |  | import play.libs.Json;
 | 
					
						
							|  |  |  | import play.mvc.BodyParser;
 | 
					
						
							|  |  |  | import play.mvc.Controller;
 | 
					
						
							|  |  |  | import play.mvc.Result;
 | 
					
						
							|  |  |  | import security.AuthUtil;
 | 
					
						
							|  |  |  | import security.AuthenticationManager;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import javax.annotation.Nonnull;
 | 
					
						
							|  |  |  | import javax.annotation.Nullable;
 | 
					
						
							|  |  |  | import javax.inject.Inject;
 | 
					
						
							|  |  |  | import javax.naming.AuthenticationException;
 | 
					
						
							|  |  |  | import javax.naming.NamingException;
 | 
					
						
							|  |  |  | import java.io.BufferedReader;
 | 
					
						
							|  |  |  | import java.io.FileReader;
 | 
					
						
							|  |  |  | import java.io.IOException;
 | 
					
						
							|  |  |  | import java.io.InputStream;
 | 
					
						
							|  |  |  | import java.nio.file.Files;
 | 
					
						
							|  |  |  | import java.nio.file.Path;
 | 
					
						
							|  |  |  | import java.nio.file.Paths;
 | 
					
						
							|  |  |  | import java.security.InvalidKeyException;
 | 
					
						
							|  |  |  | import java.security.NoSuchAlgorithmException;
 | 
					
						
							|  |  |  | import java.util.stream.Stream;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | public class Application extends Controller {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private final Config _config;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @Inject
 | 
					
						
							|  |  |  |   public Application(@Nonnull Config config) {
 | 
					
						
							|  |  |  |     _config = config;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Serves the build output index.html for any given path
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * @param path takes a path string, which essentially is ignored
 | 
					
						
							|  |  |  |    *             routing is managed client side
 | 
					
						
							|  |  |  |    * @return {Result} build output index.html resource
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   private Result serveAsset(@Nullable String path) {
 | 
					
						
							|  |  |  |     InputStream indexHtml = Play.application().classloader().getResourceAsStream("public/index.html");
 | 
					
						
							|  |  |  |     response().setHeader("Cache-Control", "no-cache");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ok(indexHtml).as("text/html");
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public Result healthcheck() {
 | 
					
						
							|  |  |  |     return ok("GOOD");
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public Result printDeps() {
 | 
					
						
							|  |  |  |     final String appHome = System.getenv("WHZ_APP_HOME");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     String libPath = appHome + "/lib";
 | 
					
						
							|  |  |  |     String commitFile = appHome + "/commit";
 | 
					
						
							|  |  |  |     String commit = "";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (appHome == null) {
 | 
					
						
							|  |  |  |       return ok("WHZ_APP_HOME environmental variable not defined");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try (BufferedReader reader = new BufferedReader(new FileReader(commitFile))) {
 | 
					
						
							|  |  |  |       commit = reader.readLine();
 | 
					
						
							|  |  |  |     } catch (IOException ioe) {
 | 
					
						
							|  |  |  |       Logger.error("Error while reading commit file. Error message: " + ioe.getMessage());
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     //get all the files from /libs directory
 | 
					
						
							|  |  |  |     StringBuilder sb = new StringBuilder();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try (Stream<Path> paths = Files.list(Paths.get(libPath))) {
 | 
					
						
							|  |  |  |       paths.filter(Files::isRegularFile).
 | 
					
						
							|  |  |  |           forEach(f -> sb.append(f.getFileName()).append("\n"));
 | 
					
						
							|  |  |  |     } catch (IOException ioe) {
 | 
					
						
							|  |  |  |       Logger.error("Error while traversing the directory. Error message: " + ioe.getMessage());
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ok("commit: " + commit + "\n" + "libraries: " + sb.toString());
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * index Action proxies to serveAsset
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * @param path takes a path string which is either index.html or the path segment after /
 | 
					
						
							|  |  |  |    * @return {Result} response from serveAsset method
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public Result index(@Nullable String path) {
 | 
					
						
							|  |  |  |     return serveAsset("");
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-05 10:53:26 -07:00
										 |  |  |   /**
 | 
					
						
							|  |  |  |    * Generic not found response
 | 
					
						
							|  |  |  |    * @param path
 | 
					
						
							|  |  |  |    * @return
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public Result apiNotFound(@Nullable String path) {
 | 
					
						
							|  |  |  |     return badRequest("{\"error\": \"API endpoint does not exist\"}");
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  |   /**
 | 
					
						
							|  |  |  |    * Creates a wrapping ObjectNode containing config information
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * @return Http Result instance with app configuration attributes
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public Result appConfig() {
 | 
					
						
							|  |  |  |     final ObjectNode config = Json.newObject();
 | 
					
						
							|  |  |  |     config.put("appVersion", _config.getString("app.version"));
 | 
					
						
							|  |  |  |     config.put("isInternal", _config.getBoolean("linkedin.internal"));
 | 
					
						
							|  |  |  |     config.put("shouldShowDatasetLineage", _config.getBoolean("linkedin.show.dataset.lineage"));
 | 
					
						
							|  |  |  |     config.put("suggestionConfidenceThreshold",
 | 
					
						
							|  |  |  |         Integer.valueOf(_config.getString("linkedin.suggestion.confidence.threshold")));
 | 
					
						
							|  |  |  |     config.set("wikiLinks", wikiLinks());
 | 
					
						
							|  |  |  |     config.set("tracking", trackingInfo());
 | 
					
						
							|  |  |  |     // In a staging environment, we can trigger this flag to be true so that the UI can handle based on
 | 
					
						
							|  |  |  |     // such config and alert users that their changes will not affect production data
 | 
					
						
							|  |  |  |     config.put("isStagingBanner", _config.getBoolean("ui.show.staging.banner"));
 | 
					
						
							|  |  |  |     config.put("isLiveDataWarning", _config.getBoolean("ui.show.live.data.banner"));
 | 
					
						
							|  |  |  |     config.put("showChangeManagement", _config.getBoolean("ui.show.CM.banner"));
 | 
					
						
							| 
									
										
										
										
											2019-09-05 10:53:26 -07:00
										 |  |  |     // Flag to enable people entity elements
 | 
					
						
							|  |  |  |     config.put("showPeople", _config.getBoolean("ui.show.people"));
 | 
					
						
							| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  |     config.put("changeManagementLink", _config.getString("ui.show.CM.link"));
 | 
					
						
							|  |  |  |     // Flag set in order to warn users that search is experiencing issues
 | 
					
						
							|  |  |  |     config.put("isStaleSearch", _config.getBoolean("ui.show.stale.search"));
 | 
					
						
							|  |  |  |     config.put("showAdvancedSearch", _config.getBoolean("ui.show.advanced.search"));
 | 
					
						
							|  |  |  |     // Flag to use the new api for browsing datasets
 | 
					
						
							|  |  |  |     config.put("useNewBrowseDataset", _config.getBoolean("ui.new.browse.dataset"));
 | 
					
						
							|  |  |  |     // show lineage graph in relationships tabs
 | 
					
						
							|  |  |  |     config.put("showLineageGraph", _config.getBoolean("ui.show.lineage.graph"));
 | 
					
						
							|  |  |  |     // show institutional memory for available entities
 | 
					
						
							|  |  |  |     config.put("showInstitutionalMemory", _config.getBoolean("ui.show.institutional.memory"));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Insert properties for user profile operations
 | 
					
						
							|  |  |  |     config.set("userEntityProps", userEntityProps());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final ObjectNode response = Json.newObject();
 | 
					
						
							|  |  |  |     response.put("status", "ok");
 | 
					
						
							|  |  |  |     response.set("config", config);
 | 
					
						
							|  |  |  |     return ok(response);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Creates a JSON object of profile / avatar properties
 | 
					
						
							|  |  |  |    * @return Json avatar / profile image properties
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   private ObjectNode userEntityProps() {
 | 
					
						
							|  |  |  |     final ObjectNode props = Json.newObject();
 | 
					
						
							|  |  |  |     props.put("aviUrlPrimary", _config.getString("linkedin.links.avi.urlPrimary"));
 | 
					
						
							|  |  |  |     props.put("aviUrlFallback", _config.getString("linkedin.links.avi.urlFallback"));
 | 
					
						
							|  |  |  |     return props;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * @return Json object with internal wiki links
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private ObjectNode wikiLinks() {
 | 
					
						
							|  |  |  |     final ObjectNode wikiLinks = Json.newObject();
 | 
					
						
							|  |  |  |     wikiLinks.put("appHelp", _config.getString("links.wiki.appHelp"));
 | 
					
						
							|  |  |  |     wikiLinks.put("gdprPii", _config.getString("links.wiki.gdprPii"));
 | 
					
						
							|  |  |  |     wikiLinks.put("tmsSchema", _config.getString("links.wiki.tmsSchema"));
 | 
					
						
							|  |  |  |     wikiLinks.put("gdprTaxonomy", _config.getString("links.wiki.gdprTaxonomy"));
 | 
					
						
							|  |  |  |     wikiLinks.put("staleSearchIndex", _config.getString("links.wiki.staleSearchIndex"));
 | 
					
						
							|  |  |  |     wikiLinks.put("dht", _config.getString("links.wiki.dht"));
 | 
					
						
							|  |  |  |     wikiLinks.put("purgePolicies", _config.getString("links.wiki.purgePolicies"));
 | 
					
						
							|  |  |  |     wikiLinks.put("jitAcl", _config.getString("links.wiki.jitAcl"));
 | 
					
						
							|  |  |  |     wikiLinks.put("metadataCustomRegex", _config.getString("links.wiki.metadataCustomRegex"));
 | 
					
						
							|  |  |  |     wikiLinks.put("exportPolicy", _config.getString("links.wiki.exportPolicy"));
 | 
					
						
							|  |  |  |     wikiLinks.put("metadataHealth", _config.getString("links.wiki.metadataHealth"));
 | 
					
						
							|  |  |  |     wikiLinks.put("purgeKey", _config.getString("links.wiki.purgeKey"));
 | 
					
						
							|  |  |  |     wikiLinks.put("datasetDecommission", _config.getString("links.wiki.datasetDecommission"));
 | 
					
						
							|  |  |  |     return wikiLinks;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * @return Json object containing the tracking configuration details
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   private ObjectNode trackingInfo() {
 | 
					
						
							|  |  |  |     final ObjectNode piwik = Json.newObject();
 | 
					
						
							|  |  |  |     piwik.put("piwikSiteId", Integer.valueOf(_config.getString("tracking.piwik.siteid")));
 | 
					
						
							|  |  |  |     piwik.put("piwikUrl", _config.getString("tracking.piwik.url"));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final ObjectNode trackers = Json.newObject();
 | 
					
						
							|  |  |  |     trackers.set("piwik", piwik);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final ObjectNode trackingConfig = Json.newObject();
 | 
					
						
							|  |  |  |     trackingConfig.set("trackers", trackers);
 | 
					
						
							|  |  |  |     trackingConfig.put("isEnabled", true);
 | 
					
						
							|  |  |  |     return trackingConfig;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public Result login() {
 | 
					
						
							|  |  |  |     //You can generate the Csrf token such as String csrfToken = SecurityPlugin.getInstance().getCsrfToken();
 | 
					
						
							|  |  |  |     String csrfToken = "";
 | 
					
						
							|  |  |  |     return serveAsset("");
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @BodyParser.Of(BodyParser.Json.class)
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public Result authenticate() throws NamingException {
 | 
					
						
							|  |  |  |     JsonNode json = request().body().asJson();
 | 
					
						
							|  |  |  |     // Extract username and password as String from JsonNode,
 | 
					
						
							|  |  |  |     //   null if they are not strings
 | 
					
						
							|  |  |  |     String username = json.findPath("username").textValue();
 | 
					
						
							|  |  |  |     String password = json.findPath("password").textValue();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (StringUtils.isBlank(username)) {
 | 
					
						
							|  |  |  |       return badRequest("Missing or invalid [username]");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     if (password == null) {
 | 
					
						
							|  |  |  |       password = "";
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     session().clear();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Create a uuid string for this session if one doesn't already exist
 | 
					
						
							|  |  |  |     //   to be appended to the Result object
 | 
					
						
							|  |  |  |     String uuid = session("uuid");
 | 
					
						
							|  |  |  |     if (uuid == null) {
 | 
					
						
							|  |  |  |       uuid = java.util.UUID.randomUUID().toString();
 | 
					
						
							|  |  |  |       session("uuid", uuid);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  |       AuthenticationManager.authenticateUser(username, password);
 | 
					
						
							|  |  |  |     } catch (AuthenticationException e) {
 | 
					
						
							|  |  |  |       Logger.warn("Authentication error!", e);
 | 
					
						
							|  |  |  |       return badRequest("Invalid Credential");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Adds the username to the session cookie
 | 
					
						
							|  |  |  |     session("user", username);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     String secretKey = _config.getString("play.http.secret.key");
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  |       //store hashed username to PLAY_SESSION cookie
 | 
					
						
							|  |  |  |       String hashedUserName = AuthUtil.generateHash(username, secretKey.getBytes());
 | 
					
						
							|  |  |  |       session("auth_token", hashedUserName);
 | 
					
						
							|  |  |  |     } catch (NoSuchAlgorithmException | InvalidKeyException e) {
 | 
					
						
							|  |  |  |       Logger.error("Failed to hash username", e);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Construct an ObjectNode with the username and uuid token to be sent with the response
 | 
					
						
							|  |  |  |     ObjectNode data = Json.newObject();
 | 
					
						
							|  |  |  |     data.put("username", username);
 | 
					
						
							|  |  |  |     data.put("uuid", uuid);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Create a new response ObjectNode to return when authenticate request is successful
 | 
					
						
							|  |  |  |     ObjectNode response = Json.newObject();
 | 
					
						
							|  |  |  |     response.put("status", "ok");
 | 
					
						
							|  |  |  |     response.set("data", data);
 | 
					
						
							|  |  |  |     return ok(response);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public Result logout() {
 | 
					
						
							|  |  |  |     session().clear();
 | 
					
						
							|  |  |  |     return ok();
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | }
 |