package org.openslx.dozmod.gui.window; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.util.Iterator; import java.util.List; import javax.swing.AbstractAction; import javax.swing.DefaultComboBoxModel; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import org.apache.log4j.Logger; import org.apache.thrift.TBaseHelper; import org.apache.thrift.TException; import org.openslx.bwlp.thrift.iface.Organization; import org.openslx.bwlp.thrift.iface.Satellite; import org.openslx.dozmod.App; import org.openslx.dozmod.Config; import org.openslx.dozmod.authentication.Authenticator; import org.openslx.dozmod.authentication.Authenticator.AuthenticationData; import org.openslx.dozmod.authentication.Authenticator.AuthenticatorCallback; import org.openslx.dozmod.authentication.EcpAuthenticator; import org.openslx.dozmod.authentication.FingerprintManager; import org.openslx.dozmod.authentication.ShibbolethEcp.ReturnCode; import org.openslx.dozmod.authentication.TestAccountAuthenticator; import org.openslx.dozmod.gui.Gui; import org.openslx.dozmod.gui.MainWindow; import org.openslx.dozmod.gui.activity.UpdatePanel; import org.openslx.dozmod.gui.helper.MessageType; import org.openslx.dozmod.gui.helper.TextChangeListener; import org.openslx.dozmod.gui.window.layout.LoginWindowLayout; import org.openslx.dozmod.thrift.Session; import org.openslx.dozmod.thrift.ThriftActions; import org.openslx.dozmod.thrift.ThriftError; import org.openslx.dozmod.thrift.cache.OrganizationCache; import org.openslx.dozmod.util.ClientVersion; import org.openslx.dozmod.util.DesktopEnvironment; import org.openslx.dozmod.util.DesktopEnvironment.Link; import org.openslx.util.QuickTimer; import org.openslx.util.QuickTimer.Task; import edu.kit.scc.dei.ecplean.ECPAuthenticationException; /** * @author Jonathan Bauer * */ @SuppressWarnings("serial") public class LoginWindow extends LoginWindowLayout { private final static Logger LOGGER = Logger.getLogger(LoginWindow.class); public static enum LoginType { ECP(0), TEST_ACCOUNT(1), DIRECT_CONNECT(2); public final int id; private LoginType(final int id) { this.id = id; } } // authentication method to use for login attempts protected LoginType loginType = null; // text constants private final String NO_USERNAME = "Kein Benutzername angegeben!"; private final String NO_PASSWORD = "Kein Passwort angegeben!"; private boolean forceCustomSatellite = false; public LoginWindow(Frame modalParent) { // call the constructor of the superclass super(modalParent); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { Gui.exit(0); } }); // first do all listeners stuff for (final LoginType type : LoginType.values()) { rdoLoginType[type.id].setActionCommand(type.toString()); rdoLoginType[type.id].addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { cboOrganization.setEnabled(cboOrganization.getModel().getSize() != 0 && type == LoginType.ECP); loginType = type; btnOpenRegistration.setEnabled(type == LoginType.ECP); } } }); } // check if we had saved an authentication method String savedAuthMethod = Config.getAuthenticationMethod(); LoginType savedLoginType; try { savedLoginType = LoginType.valueOf(savedAuthMethod); } catch (Exception e) { // if no valid LOGIN_TYPE was saved, just enable the BWIDM button savedLoginType = LoginType.ECP; } if (savedLoginType == LoginType.ECP) { // disable login button til the idp list is here enableLogin(false); } // While filling, disable cboOrganization.setEnabled(false); // Not yet implemented, disable rdoLoginType[LoginType.DIRECT_CONNECT.id].setEnabled(false); // enable the corresponding button rdoLoginType[savedLoginType.id].setSelected(true); loginType = savedLoginType; QuickTimer.scheduleOnce(new Task() { List orgs = null; @Override public void fire() { try { // Wait for proxy server init App.waitForInit(); orgs = OrganizationCache.getAll(); } catch (Exception e) { LOGGER.error("Error during execution: ", e); } // filter out every organisation without ecp Iterator iterator = orgs.iterator(); while (iterator.hasNext()) { Organization current = iterator.next(); if (current == null || !current.isSetEcpUrl() || current.getEcpUrl().isEmpty()) iterator.remove(); } // now send the organisations back to the LoginWindow // through populateIdpCombo() Gui.asyncExec(new Runnable() { @Override public void run() { populateIdpCombo(orgs); LoginWindow.this.pack(); enableLogin(true); } }); } }); btnLogin.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int mods = e.getModifiers(); // evaluate if SHIFT was hold during the click forceCustomSatellite = ((mods & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK); doLogin(); } }); btnOpenRegistration.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DesktopEnvironment.openWebpage(Link.REGISTER_BWIDM); } }); // make enter key activate login pnlLoginForm.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "login"); pnlLoginForm.getActionMap().put("login", new AbstractAction() { @Override public void actionPerformed(ActionEvent ae) { btnLogin.doClick(); } }); btnSettings.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ConfigWindow.open(LoginWindow.this); } }); btnLogDir.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DesktopEnvironment.openLocal(new File(Config.getPath())); } }); btnUpdateCheck.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { CheckUpdateWindow.open(SwingUtilities.getWindowAncestor(btnUpdateCheck)); } }); txtUsername.getDocument().addDocumentListener(new TextChangeListener() { @Override public void changed() { if (cboOrganization.getSelectedIndex() != -1 || loginType != LoginType.ECP) return; String name = txtUsername.getText(); int at = name.indexOf('@'); if (at == -1) return; final int oldCursorPos = txtUsername.getCaretPosition(); String suffix = name.substring(at + 1); Organization organization = OrganizationCache.find(suffix); if (organization == null) return; final String nameOnly = name.substring(0, at); cboOrganization.setSelectedItem(organization); Gui.asyncExec(new Runnable() { @Override public void run() { txtUsername.setText(nameOnly); txtUsername.setCaretPosition(Math.min(oldCursorPos, nameOnly.length())); } }); } }); QuickTimer.scheduleOnce(new Task() { @Override public void fire() { if (!ClientVersion.isNewest()) { Gui.asyncExec(new Runnable() { @Override public void run() { btnUpdateCheck.setText("!! UPDATE !!"); } }); } } }); } /** * Called by the thread fetching the organization list from the cache * * @param orgs list of organization to show in the combo box */ public void populateIdpCombo(List orgs) { // sanity checks on orgs if (orgs == null) { LOGGER.error("No organizations received from the cache."); return; } cboOrganization.setModel(new DefaultComboBoxModel(orgs.toArray(new Organization[orgs.size()]))); // now check if we had a saved identity provider String savedOrganizationId = Config.getIdentityProvider(); cboOrganization.setSelectedItem(OrganizationCache.find(savedOrganizationId)); cboOrganization.setEnabled(cboOrganization.getModel().getSize() != 0 && loginType == LoginType.ECP); } /** * Saves various user choices to the config file. * This gets triggered when the login button is activated. */ private void doSaveConfig() { // first we need to check if the "Remember me" button is active if (chkSaveUsername.isSelected()) { // save username String username = txtUsername.getText(); if (!username.isEmpty()) { Config.setUsername(username); } } else { Config.setUsername(""); } // always save the authentication method and potentially the identity provider Config.setAuthenticationMethod(loginType.toString()); // save the selected identity provider Organization selectedOrg = cboOrganization.getItemAt(cboOrganization.getSelectedIndex()); if (selectedOrg != null) { Config.setIdentityProvider(selectedOrg.organizationId); } } /** * Actually do the login using the username/password in the field using the * authentication mechanism corresponding to the selected authButton * * @throws ECPAuthenticationException */ private void doLogin() { // sanity check on loginType. if (loginType == null) { Gui.showMessageBox(this, "Bitte wählen Sie eine Authentifizierungsart.", MessageType.ERROR, LOGGER, null); return; } if (loginType == LoginType.ECP && cboOrganization.getSelectedIndex() == -1) { Gui.showMessageBox(this, "Bitte wählen Sie ihre Organisation als 'Identity Provider'.", MessageType.ERROR, LOGGER, null); cboOrganization.requestFocusInWindow(); return; } // here we only check for the fields String username = txtUsername.getText(); final String password = String.copyValueOf(txtPassword.getPassword()); // login clicked, lets first read the fields if (username.isEmpty()) { Gui.showMessageBox(this, NO_USERNAME, MessageType.ERROR, LOGGER, null); return; } if (password.isEmpty()) { Gui.showMessageBox(this, NO_PASSWORD, MessageType.ERROR, LOGGER, null); return; } // determine which organization was selected by the user. Organization selectedOrg = cboOrganization.getItemAt(cboOrganization.getSelectedIndex()); // we are doing the login soon, first save the config doSaveConfig(); // Setup login callback final LoginWindow me = this; final AuthenticatorCallback authenticatorCallback = new AuthenticatorCallback() { @Override public void postLogin(ReturnCode returnCode, final AuthenticationData data, Throwable t) { switch (returnCode) { case NO_ERROR: Gui.asyncExec(new Runnable() { @Override public void run() { postSuccessfulLogin(data); } }); return; case IDENTITY_PROVIDER_ERROR: Gui.showMessageBox(me, "IdP Error", MessageType.ERROR, LOGGER, null); break; case SERVICE_PROVIDER_ERROR: // here if we have t != null then we have not received a token // if we have t, then the token is invalid. Gui.showMessageBox(me, "Invalid token from the service provider!", MessageType.ERROR, LOGGER, t); break; case UNREGISTERED_ERROR: LOGGER.error("User not registered!"); BwIdmLinkWindow.open((JFrame) SwingUtilities.getWindowAncestor(me)); break; case INVALID_URL_ERROR: Gui.showMessageBox(me, "ECP Authenticator says: Invalid URL.", MessageType.ERROR, LOGGER, t); break; case GENERIC_ERROR: default: if (t == null || !t.getClass().equals(RuntimeException.class)) { Gui.showMessageBox(me, "Internal error!", MessageType.ERROR, null, t); } else { Gui.showMessageBox(me, "Der Masterserver hat den Loginversuch mit der" + " folgenden Nachricht abgewiesen:\n\n" + t.getMessage(), MessageType.ERROR, null, null); } break; } enableLogin(true); } }; // now switch over the login types. final Authenticator authenticator; switch (loginType) { case ECP: authenticator = new EcpAuthenticator(selectedOrg.getEcpUrl()); break; case TEST_ACCOUNT: authenticator = new TestAccountAuthenticator(); break; case DIRECT_CONNECT: Gui.showMessageBox(this, "Not yet implemented", MessageType.ERROR, LOGGER, null); return; default: Gui.showMessageBox(this, "No login type selected!", MessageType.ERROR, LOGGER, null); return; } enableLogin(false); final String finalUsername = username; QuickTimer.scheduleOnce(new Task() { @Override public void fire() { // Execute login App.waitForInit(); try { authenticator.login(finalUsername, password, authenticatorCallback); return; } catch (TException e) { ThriftError.showMessage(LoginWindow.this, LOGGER, e, "Anmeldung fehlgeschlagen"); } catch (Exception e) { Gui.showMessageBox(LoginWindow.this, "Anmeldung fehlgeschlagen", MessageType.ERROR, LOGGER, e); } enableLogin(true); } }); } /** * Functions called by doLogin is the login process succeeded. * * @param data */ private void postSuccessfulLogin(AuthenticationData data) { LOGGER.info(loginType.toString() + " succeeded, token " + data.satelliteToken); // Update known suggested fingerprints importFingerprints(data.satellites); // now try to init the session with the data received if (ThriftActions.initSession(data, forceCustomSatellite, SwingUtilities.getWindowAncestor(this))) { if (chkSaveUsername.isSelected()) { Config.saveCurrentSession(Session.getSatelliteAddress(), Session.getSatelliteToken(), Session.getMasterToken()); } dispose(); return; } enableLogin(true); } private void enableLogin(boolean enable) { btnLogin.setEnabled(enable); txtUsername.setEnabled(enable); txtPassword.setEnabled(enable); pnlLoginType.setEnabled(enable); pnlLoginForm.setEnabled(enable); chkSaveUsername.setEnabled(enable); if (enable) { // Do this here, otherwise the focus might not be set String savedUsername = Config.getUsername(); if (savedUsername != null && !savedUsername.isEmpty()) { txtUsername.setText(savedUsername); chkSaveUsername.setSelected(true); txtPassword.requestFocusInWindow(); } else { txtUsername.requestFocusInWindow(); } } } /** * If master server supplies certificate finger prints for a satellite, * store them so we can verify later. * * @param satellites */ private void importFingerprints(List satellites) { if (satellites == null || satellites.isEmpty()) return; for (Satellite sat : satellites) { if (sat.addressList == null || sat.certSha256 == null || sat.addressList.isEmpty()) continue; byte[] fingerprint = TBaseHelper.byteBufferToByteArray(sat.certSha256); for (String address : sat.addressList) { FingerprintManager.saveSuggestedFingerprint(address, fingerprint); } } } @SuppressWarnings("deprecation") @Override public void show() { if (!isVisible()) { pack(); MainWindow.centerShell(this); } super.show(); } /** * Opens the login window */ public static void open(Frame modalParent) { LoginWindow win = new LoginWindow(modalParent); win.setVisible(true); } }