package gov.nasa.jpl.mbee.mdk.util;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.nomagic.magicdraw.core.Application;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.task.ProgressStatus;
import com.nomagic.task.RunnableWithProgress;
import com.nomagic.ui.ProgressStatusRunner;
import gov.nasa.jpl.mbee.mdk.http.ServerException;
import gov.nasa.jpl.mbee.mdk.json.JacksonUtils;
import gov.nasa.jpl.mbee.mdk.mms.MMSUtils;
import gov.nasa.jpl.mbee.mdk.mms.actions.MMSLogoutAction;
import org.apache.http.client.utils.URIBuilder;
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TicketUtils {
private static String username = "";
private static String password = "";
private static final int TICKET_RENEWAL_INTERVAL = 15 * 60; //seconds
private static final HashMap<Project, TicketMapping> ticketMappings = new HashMap<>();
/**
* Accessor for stored username.
*
* @return username
*/
public static String getUsername() {
return username;
}
/**
* Convenience method for checking if ticket is non-empty. Used as a shorthand to verify that a user is logged in to MMS
*
* @return ticket exists and is non-empty.
*/
public static boolean isTicketSet(Project project) {
TicketMapping ticketMap = ticketMappings.get(project);
return ticketMap != null && ticketMap.getTicket() != null && !ticketMap.getTicket().isEmpty();
}
public static boolean isTicketValid(Project project, ProgressStatus progressStatus) throws ServerException, IOException, URISyntaxException {
if (!isTicketSet(project)) {
return false;
}
String ticket = ticketMappings.get(project).getTicket();
return MMSUtils.validateCredentials(project, ticket, progressStatus).equals(username);
}
/**
* Accessor for ticket field.
*
* @return ticket string
*/
public static String getTicket(Project project) {
if (isTicketSet(project)) {
return ticketMappings.get(project).getTicket();
}
return null;
}
/**
* Logs in to MMS, using pre-specified credentials or prompting the user for new credentials.
* <p>
* If username and password have been pre-specified, will not display the dialog even if popups are
* enabled. Else will display the login dialog and use the returned value.
*
* @return TRUE if successfully logged in to MMS, FALSE otherwise.
* Will always return FALSE if popups are disabled and username/password are not pre-specified
*/
public static boolean acquireMmsTicket(Project project) {
if (!username.isEmpty() && !password.isEmpty()) {
return acquireTicket(project, password);
}
else if (!Utils.isPopupsDisabled()) {
String password = getUserCredentialsDialog();
if (password == null) {
return false;
}
return acquireTicket(project, password);
}
else {
Application.getInstance().getGUILog().log("[ERROR] Unable to login to MMS. No credentials have been specified, and dialog popups are disabled.");
return false;
}
}
/**
* Shows a login dialog window and uses its filled in values to set the username and password.
* Stores the entered username for future use / convenience, passes the entered password to acquireTicket().
*/
private static String getUserCredentialsDialog() {
// Pop up dialog for logging into Alfresco
JPanel userPanel = new JPanel();
userPanel.setLayout(new GridLayout(2, 2));
JLabel usernameLbl = new JLabel("Username:");
JLabel passwordLbl = new JLabel("Password:");
JTextField usernameFld = new JTextField();
JPasswordField passwordFld = new JPasswordField();
userPanel.add(usernameLbl);
userPanel.add(usernameFld);
userPanel.add(passwordLbl);
userPanel.add(passwordFld);
if (username != null) {
usernameFld.setText(username);
usernameFld.requestFocus();
}
passwordFld.setText("");
makeSureUserGetsFocus(usernameFld);
// isDisplayed = true;
int response = JOptionPane.showConfirmDialog(Application.getInstance().getMainFrame(), userPanel,
"MMS Credentials", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
// isDisplayed = false;
if (response == JOptionPane.OK_OPTION) {
username = usernameFld.getText();
String pass = new String(passwordFld.getPassword());
return pass;
}
else if (response == JOptionPane.CANCEL_OPTION || response == JOptionPane.CLOSED_OPTION) {
Application.getInstance().getGUILog().log("[INFO] MMS login has been cancelled.");
}
return null;
}
/**
* Forces focus to a particular JTextField in a displayed dialog
*
* @param field The field to force into focus
*/
private static void makeSureUserGetsFocus(final JTextField field) {
//from http://stackoverflow.com/questions/14096140/how-to-set-default-input-field-in-joptionpane
field.addHierarchyListener(new HierarchyListener() {
HierarchyListener hierarchyListener = this;
@Override
public void hierarchyChanged(HierarchyEvent e) {
JRootPane rootPane = SwingUtilities.getRootPane(field);
if (rootPane != null) {
final JButton okButton = rootPane.getDefaultButton();
if (okButton != null) {
okButton.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (!e.isTemporary()) {
field.requestFocusInWindow();
field.removeHierarchyListener(hierarchyListener);
okButton.removeFocusListener(this);
}
}
});
}
}
}
});
}
/**
* Method to set username and password for logging in to MMS. Can be called directly to pre-set the credential
* information for automation, but should be used with Utils.disablePopups(true) to prevent display of the standard
* log in window. Use without disabling popups will cause these values to be overwritten by the values obtained
* from the popup window call.
*/
public static void setUsernameAndPassword(String user, String pass) {
if (user == null) {
user = "";
}
if (pass == null) {
pass = "";
}
username = user;
password = pass;
}
/**
* Clears username, password, and ticket
*/
public static void clearTicket(Project project) {
password = "";
TicketMapping removed = ticketMappings.remove(project);
// kill auto-renewal in removed
if (removed != null) {
removed.getTicketRenewer().shutdown();
}
}
/**
* Uses the stored username and passed password to query MMS for a ticket. Will clear any stored password on attempt.
* <p>
* Will first check to see if there is an existing ticket, and if so if it is valid. If valid, will not resend
* for new ticket. If invalid or not present, will send for new ticket.
* <p>
* Since it can only be called by logInToMMS(), assumes that the username and password were recently
* acquired from the login dialogue or pre-specified if that's disabled.
*/
private static boolean acquireTicket(Project project, String pass) {
if (username == null || username.isEmpty()) {
Application.getInstance().getGUILog().log("[ERROR] Unable to log in to MMS without a username.");
return false;
}
if (pass == null) {
return false;
}
if (isTicketSet(project)) {
Application.getInstance().getGUILog().log("[INFO] Clearing previous credentials.");
MMSLogoutAction.logoutAction(project);
}
//ensure ticket is cleared in case of failure
ticketMappings.remove(project);
// build request
URIBuilder requestUri = MMSUtils.getServiceUri(project);
if (requestUri == null) {
return false;
}
requestUri.setPath(requestUri.getPath() + "/api/login");
requestUri.clearParameters();
ObjectNode credentials = JacksonUtils.getObjectMapper().createObjectNode();
credentials.put("username", username);
credentials.put("password", pass);
// do request
ProgressStatusRunner.runWithProgressStatus(new RunnableWithProgress() {
@Override
public void run(ProgressStatus progressStatus) {
String ticket;
try {
ticket = MMSUtils.sendCredentials(project, username, pass, progressStatus);
} catch (ServerException | IOException | URISyntaxException e) {
Application.getInstance().getGUILog().log("[ERROR] Unexpected error while acquiring credentials. Reason: " + e.getMessage());
e.printStackTrace();
return;
}
// parse response
if (ticket != null) {
ticketMappings.put(project, new TicketMapping(project, ticket));
}
}
}, "Logging in to MMS", true, 0);
// parse response
password = "";
if (ticketMappings.get(project) != null && !ticketMappings.get(project).getTicket().isEmpty()) {
return true;
}
Application.getInstance().getGUILog().log("[ERROR] Unable to log in to MMS with the supplied credentials.");
return false;
}
private static class TicketMapping {
private String ticket;
private ScheduledExecutorService ticketRenewer;
TicketMapping(final Project project, String ticket) {
this.ticket = ticket;
this.ticketRenewer = Executors.newScheduledThreadPool(1);
// intentionally catching exceptions here, to avoid scheduled thread suspension
final Runnable renewTicket = () -> {
// try/catching here to prevent service being disabled for future calls
try {
try {
boolean isValid = isTicketValid(project, null);
if (!isValid) {
Application.getInstance().getGUILog().log("[INFO] MMS credentials are expired or invalid.");
MMSLogoutAction.logoutAction(project);
}
} catch (ServerException | IOException | URISyntaxException e) {
Application.getInstance().getGUILog().log("[ERROR] Unexpected error checking ticket validity (ticket will be retained). Reason: " + e.getMessage());
e.printStackTrace();
}
} catch (Exception ignored) {
}
};
this.ticketRenewer.scheduleAtFixedRate(renewTicket, TICKET_RENEWAL_INTERVAL, TICKET_RENEWAL_INTERVAL, TimeUnit.SECONDS);
}
public String getTicket() {
return this.ticket;
}
public ScheduledExecutorService getTicketRenewer() {
return this.ticketRenewer;
}
}
}