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.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JFrame;
import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.Organization;
import org.openslx.bwlp.thrift.iface.SatelliteServer.Client;
import org.openslx.bwlp.thrift.iface.TAuthorizationException;
import org.openslx.bwlp.thrift.iface.TInternalServerError;
import org.openslx.bwlp.thrift.iface.WhoamiInfo;
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.ShibbolethEcp;
import org.openslx.dozmod.authentication.ShibbolethEcp.ReturnCode;
import org.openslx.dozmod.authentication.TestAccountAuthenticator;
import org.openslx.dozmod.gui.GraphicalCertHandler;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.MainWindow;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.gui.window.layout.LoginWindowLayout;
import org.openslx.dozmod.thrift.OrganizationCache;
import org.openslx.dozmod.thrift.Session;
import org.openslx.thrifthelper.ThriftManager;
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);
// TODO This has nothing to to with the layout
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!";
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()) {
loginTypes[type.id].setActionCommand(type.toString());
loginTypes[type.id].addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
idpCombo.setEnabled(idpCombo.getModel().getSize() != 0 && type == LoginType.ECP);
loginType = type;
}
}
});
}
// 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
loginButton.setEnabled(false);
}
// While filling, disable
idpCombo.setEnabled(false);
// Not yet implemented, disable
loginTypes[LoginType.DIRECT_CONNECT.id].setEnabled(false);
// enable the corresponding button
loginTypes[savedLoginType.id].setSelected(true);
loginType = savedLoginType;
QuickTimer.scheduleOnce(new Task() {
List<Organization> 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<Organization> iterator = orgs.iterator();
while (iterator.hasNext()) {
Organization current = iterator.next();
if (current == null | !current.isSetEcpUrl() | current.getEcpUrl().isEmpty())
iterator.remove();
}
// all fine, lets sort it
Collections.sort(orgs, new Comparator<Organization>() {
public int compare(Organization o1, Organization o2) {
return o1.getDisplayName().compareTo(o2.getDisplayName());
}
});
// now send the organisations back to the LoginWindow
// through populateIdpCombo()
Gui.asyncExec(new Runnable() {
@Override
public void run() {
populateIdpCombo(orgs);
loginButton.setEnabled(true);
}
});
}
});
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doLogin();
}
});
// add a key listener to the password field to trigger login
// when the user presses the ENTER key.
KeyAdapter loginOnEnter = new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
super.keyReleased(e);
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
doLogin();
}
}
};
passwordField.addKeyListener(loginOnEnter);
saveUsernameCheck.addKeyListener(loginOnEnter);
// finally check if we had a saved username
String savedUsername = Config.getUsername();
if (savedUsername != null && !savedUsername.isEmpty()) {
usernameField.setText(savedUsername);
saveUsernameCheck.setSelected(true);
passwordField.requestFocus();
} else {
usernameField.requestFocus();
}
}
/**
* 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<Organization> orgs) {
// sanity checks on orgs
if (orgs == null) {
LOGGER.error("No organizations received from the cache.");
return;
}
idpCombo.setModel(new DefaultComboBoxModel<Organization>(orgs.toArray(new Organization[orgs.size()])));
// now check if we had a saved identity provider
String savedOrganizationId = Config.getIdentityProvider();
idpCombo.setSelectedItem(OrganizationCache.find(savedOrganizationId));
idpCombo.setEnabled(idpCombo.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 (saveUsernameCheck.isSelected()) {
// save username
String username = usernameField.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 = idpCombo.getItemAt(idpCombo.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, "No login type set, a default should be set! Ignoring...",
MessageType.ERROR, LOGGER, null);
return;
}
// we are doing the login soon, first save the config
doSaveConfig();
// here we only check for the fields
String username = usernameField.getText();
String password = String.copyValueOf(passwordField.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 = idpCombo.getItemAt(idpCombo.getSelectedIndex());
// Setup login callback
final LoginWindow me = this;
AuthenticatorCallback authenticatorCallback = new AuthenticatorCallback() {
@Override
public void postLogin(ReturnCode returnCode, AuthenticationData data, Throwable t) {
switch (returnCode) {
case NO_ERROR:
postSuccessfulLogin(data);
break;
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:
Gui.showMessageBox(me, "You are not registered to bwLehrpool. Please visit "
+ ShibbolethEcp.getRegistrationUrl() + " and register first.", MessageType.ERROR,
LOGGER, t);
break;
case INVALID_URL_ERROR:
Gui.showMessageBox(me, "ECP Authenticator says: Invalid URL.", MessageType.ERROR, LOGGER,
t);
break;
case GENERIC_ERROR:
default:
Gui.showMessageBox(me, "Internal error!", MessageType.ERROR, LOGGER, null);
break;
}
}
};
// now switch over the login types.
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;
}
// Excute login
try {
authenticator.login(username, password, authenticatorCallback);
} catch (Exception e) {
Gui.showMessageBox(this, "Authentication failed: " + e.getMessage(), MessageType.ERROR, LOGGER, e);
return;
}
}
/**
* Functions called by doLogin is the login process succeeded.
*
* @param data
*/
private void postSuccessfulLogin(AuthenticationData data) {
LOGGER.info(loginType.toString() + " succeeded, token " + data.satelliteToken);
// TODO: Show satellite selection if > 1
//String satAddress = data.satellites.get(0).addressList.get(0);
String satAddress = "132.230.8.113"; // TODO: HACK HACK
Client client = ThriftManager.getNewSatelliteClient(GraphicalCertHandler.getSslContext(satAddress), satAddress,
App.THRIFT_SSL_PORT, App.THRIFT_TIMEOUT_MS);
if (client == null) {
Gui.showMessageBox(this, "Login erfolgreich, aber der Satellit antwortet nicht",
MessageType.ERROR, LOGGER, null);
return;
}
WhoamiInfo whoami = null;
Exception e = null;
try {
whoami = client.whoami(data.satelliteToken);
} catch (TAuthorizationException e1) {
Gui.showMessageBox(this,
"Authentifizierung erfolgreich, der Satellit verweigert jedoch die Verbindung.\n\n"
+ "Grund: " + e1.number.toString() + " (" + e1.message + ")", MessageType.ERROR,
null, null);
return;
} catch (TInternalServerError e1) {
Gui.showMessageBox(
this,
"Authentifizierung erfolgreich, bei der Kommunikation mit dem Satelliten trat jedoch ein interner Server-Fehler auf.",
MessageType.ERROR, LOGGER, e);
return;
} catch (Exception ex) {
e = ex;
}
if (whoami != null) {
Session.initialize(whoami, satAddress, data.satelliteToken, data.masterToken);
ThriftManager.setSatelliteAddress(GraphicalCertHandler.getSslContext(Session.getSatelliteAddress()),
Session.getSatelliteAddress(), App.THRIFT_SSL_PORT, App.THRIFT_TIMEOUT_MS);
// now read the config to see if the user already agreed to the disclaimer
// if (DisclaimerWindow.shouldBeShown())
// VirtualizerNoticeWindow.open();
// Save session (TODO: Extra checkbox)
if (saveUsernameCheck.isSelected()) {
Config.saveCurrentSession(Session.getSatelliteAddress(), Session.getSatelliteToken(),
Session.getMasterToken());
}
dispose();
return;
}
Gui.showMessageBox(this,
"Authentifizierung erfolgreich, aber der Satellit akzeptiert das Sitzungstoken nicht.",
MessageType.ERROR, LOGGER, e);
}
@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);
}
}