Dépôt officiel du code source de l'ERP OpenConcerto
Blame | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.login;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.ReloadPanel;
import org.openconcerto.ui.SwingThrottle;
import org.openconcerto.ui.component.combo.ISearchableCombo;
import org.openconcerto.ui.valuewrapper.EmptyValueWrapper;
import org.openconcerto.ui.valuewrapper.ValueWrapperFactory;
import org.openconcerto.utils.Base64;
import org.openconcerto.utils.JImage;
import org.openconcerto.utils.i18n.TM;
import org.openconcerto.utils.i18n.TranslationManager;
import org.openconcerto.utils.jsonrpc.JSONRPCClient;
import org.openconcerto.utils.text.SimpleDocumentListener;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
public class LoginFrame extends JFrame implements ActionListener, ConnectionStateListener {
public static final int CONNECTION_NOT_INITIATED = -1;
public static final int CONNECTION_INPROGRESS = 0;
public static final int CONNECTION_OK = 1;
public static final int CONNECTION_TIMEOUT = 2;
public static final int CONNECTION_REFUSED = 3;
public static final int CONNECTION_BROKEN = 4;
private final LoginProperties loginProperties;
// Login
private EmptyValueWrapper<String> textLogin;
// Password
private JPasswordField textPassWord;
private String encryptedPassword;
private String clearPassword;
private boolean allowStoredPass = true;
// Company
private ISearchableCombo<Company> comboCompany;
private ReloadPanel reloadPanel;
private CompanyIListModel model;
// 1/ open an empty interface, rotate reloadpanel
// 2/ fill with stored login/password/ defaultcompany (id+name)
// 3a/ when connection to server is ready, stop reloadpanel
// 3b/ when login+pass are entered, retrieve allowed companies and enable
// connect when
// connection to server is ready
private final JCheckBox saveCheckBox = new JCheckBox(getTM().translate("loginPanel.storePass"));
private final JButton buttonConnect = new JButton(getTM().translate("loginPanel.loginAction"));
private final JLabel loginLabel = new JLabel(getTM().translate("loginPanel.loginLabel"));
private final JLabel passwordLabel = new JLabel(getTM().translate("loginPanel.passLabel"));
private final JLabel companyLabel = new JLabel(getTM().translate("loginPanel.companyLabel"));
private String localeBaseName = null;
private final List<Locale> localesToDisplay = new ArrayList<Locale>();
private final JButton langButton = new JButton(Locale.ROOT.getLanguage());
private String company;
private final JSONRPCClient client;
private int connectionState = CONNECTION_NOT_INITIATED;
private final ExecutorService executorService = Executors.newFixedThreadPool(1);
private final String serverUrl;
private String context;
private int idCompany;
private SwingThrottle tCheckValidity;
public LoginFrame(String serverUrl, final JSONRPCClient client, String context) throws IOException {
this.client = client;
this.serverUrl = serverUrl;
this.context = context;
this.setContentPane(createLoginPanel());
// load stored info
System.err.println("LoginFrame.LoginFrame()" + serverUrl);
this.loginProperties = new LoginProperties(serverUrl);
this.loginProperties.load();
final Company storedDefaultCompany = new Company(this.loginProperties.getLastCompanyId(), this.loginProperties.getLastCompanyName());
this.init(this.loginProperties.getLastLoginName(), this.loginProperties.getEncryptedStoredPassword(), storedDefaultCompany);
}
public synchronized int getConnectionState() {
return this.connectionState;
}
public synchronized void setConnectionState(int connectionState) {
this.connectionState = connectionState;
}
private TM getTM() {
return org.openconcerto.utils.i18n.TM.getInstance();
}
private void init(String storedLogin, String storedEncryptedPassword, Company storedDefaultCompany) {
this.textLogin.setValue(storedLogin);
if (this.saveCheckBox != null && storedEncryptedPassword != null && storedEncryptedPassword.length() > 0) {
this.saveCheckBox.setSelected(true);
}
// to show the user its password has been retrieved
if (storedEncryptedPassword != null) {
final char[] s = new char[8];
Arrays.fill(s, ' ');
this.textPassWord.setText(new String(s));
this.clearPassword = null;
} else
this.clearPassword = "";
this.model = new CompanyIListModel();
this.comboCompany.initCache(this.model);
this.tCheckValidity = new SwingThrottle(800, new Runnable() {
@Override
public void run() {
checkValidity();
}
});
}
private JPanel createLoginPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.gridheight = 1;
c.gridwidth = 2;
c.gridx = 0;
c.gridy = 0;
c.weightx = 1;
c.weighty = 0;
c.insets = new Insets(0, 0, 0, 0);
c.fill = GridBagConstraints.HORIZONTAL;
// Logo
JImage imageLogo = new JImage(LoginFrame.class.getResource("OpenConcerto_login.png"));
imageLogo.setBackground(Color.WHITE);
imageLogo.check();
panel.add(imageLogo, c);
c.gridy++;
c.gridwidth = GridBagConstraints.REMAINDER;
panel.add(new JSeparator(SwingConstants.HORIZONTAL), c);
// Login
c.insets = new Insets(2, 2, 1, 2);
c.gridy++;
c.gridwidth = 1;
c.weightx = 0;
this.loginLabel.setHorizontalAlignment(SwingConstants.RIGHT);
panel.add(this.loginLabel, c);
this.textLogin = new EmptyValueWrapper<String>(ValueWrapperFactory.create(new JTextField(), String.class));
c.gridx++;
c.weightx = 1;
panel.add(this.textLogin.getComp(), c);
((JTextField) this.textLogin.getComp()).addActionListener(this);
this.textLogin.addValueListener(new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
LoginFrame.this.tCheckValidity.execute();
}
});
// Password
c.gridy++;
c.gridx = 0;
c.weightx = 0;
System.err.println("LoginFrame.createLoginPanel()22c");
this.passwordLabel.setHorizontalAlignment(SwingConstants.RIGHT);
panel.add(this.passwordLabel, c);
this.textPassWord = new JPasswordField();
c.gridx++;
c.weightx = 1;
panel.add(this.textPassWord, c);
this.textPassWord.getDocument().addDocumentListener(new SimpleDocumentListener() {
public void update(final DocumentEvent e) {
LoginFrame.this.clearPassword = String.valueOf(LoginFrame.this.textPassWord.getPassword());
LoginFrame.this.tCheckValidity.execute();
}
});
this.textPassWord.addActionListener(this);
System.err.println("LoginFrame.createLoginPanel()22d");
// Societe
c.gridy++;
c.gridx = 0;
c.weightx = 0;
this.companyLabel.setHorizontalAlignment(SwingConstants.RIGHT);
panel.add(this.companyLabel, c);
this.comboCompany = new ISearchableCombo<Company>(true);
this.comboCompany.setEnabled(false);
c.gridx++;
panel.add(this.comboCompany, c);
// Button
final JPanel panelButton = new JPanel();
panelButton.setOpaque(false);
panelButton.setLayout(new GridBagLayout());
final GridBagConstraints c2 = new DefaultGridBagConstraints();
c2.weightx = 1;
if (this.allowStoredPass) {
this.saveCheckBox.setOpaque(false);
panelButton.add(this.saveCheckBox, c2);
c2.weightx = 0;
}
c2.gridx++;
// Language selector
//
this.langButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final JPopupMenu menu = new JPopupMenu();
final Locale locale = Locale.getDefault();
for (final Locale l : LoginFrame.this.localesToDisplay) {
final JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(l.getDisplayName(l));
if (l.equals(locale)) {
menuItem.setSelected(true);
}
menu.add(menuItem);
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setUILanguage(l);
}
});
}
menu.show(LoginFrame.this.langButton, 0, 0);
}
});
this.langButton.setOpaque(false);
this.langButton.setBorderPainted(false);
this.langButton.setContentAreaFilled(false);
this.langButton.setBorder(null);
this.langButton.setFocusable(false);
this.langButton.setVisible(false);
panelButton.add(this.langButton, c2);
c2.gridx++;
this.reloadPanel = new ReloadPanel();
this.reloadPanel.setOpaque(false);
this.reloadPanel.setMode(ReloadPanel.MODE_EMPTY);
panelButton.add(this.reloadPanel, c2);
c2.gridx++;
c2.weightx = 0;
this.buttonConnect.setEnabled(false);
this.buttonConnect.setOpaque(false);
panelButton.add(this.buttonConnect, c2);
c.gridy++;
c.gridx = 0;
c.weightx = 0;
c.gridwidth = GridBagConstraints.REMAINDER;
c.weighty = 1;
panel.add(panelButton, c);
this.buttonConnect.addActionListener(this);
initLocalization("org.openconcerto.ui.login.Messages",
Arrays.asList(Locale.FRANCE, Locale.CANADA_FRENCH, new Locale("fr", "CH"), new Locale("fr", "BE"), Locale.UK, Locale.CANADA, Locale.US, Locale.GERMANY, new Locale("de", "CH")));
setUILanguage(Locale.getDefault());
return panel;
}
private void checkValidity() {
String login = this.textLogin.getValue();
this.buttonConnect.setEnabled(login != null && !login.trim().isEmpty() && this.comboCompany.getSelectedItem() != null);
// this.buttonConnect.setEnabled(this.connectionAllowed == null &&
// this.areFieldsValidated());
// this.buttonConnect.setToolTipText(this.connectionAllowed);
System.err.println("LoginFrame.checkValidity()");
String ePassword = this.encryptedPassword;
if (ePassword == null) {
if (this.clearPassword == null) {
this.clearPassword = "";
}
ePassword = encodePassword(this.clearPassword);
}
// Empty login is not allowed
if (login == null || login.trim().isEmpty()) {
return;
}
this.reloadPanel.setMode(ReloadPanel.MODE_ROTATE);
try {
sendAllowedCompanyRequest(login, ePassword);
} catch (Exception e) {
// Connection to server impossible
this.reloadPanel.setMode(ReloadPanel.MODE_BLINK);
e.printStackTrace();
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (this.comboCompany.getSelectedItem() == null) {
return;
}
// Connect
this.setEnabled(false);
this.reloadPanel.setMode(ReloadPanel.MODE_ROTATE);
String ePassword = this.encryptedPassword;
if (ePassword == null) {
if (this.clearPassword == null) {
this.clearPassword = "";
}
ePassword = encodePassword(this.clearPassword);
}
this.company = this.comboCompany.getSelectedItem().getName();
sendLoginRequest(this.textLogin.getValue(), ePassword, this.comboCompany.getSelectedItem().getId(), this.context);
}
@Override
public void stateChanged(final int oldState, final int newState) {
if (SwingUtilities.isEventDispatchThread()) {
throw new IllegalAccessError("Must not be called in EventDispatchThread");
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (newState == CONNECTION_REFUSED || newState == CONNECTION_TIMEOUT) {
LoginFrame.this.reloadPanel.setMode(ReloadPanel.MODE_BLINK);
} else if (newState == CONNECTION_OK) {
LoginFrame.this.reloadPanel.setMode(ReloadPanel.MODE_EMPTY);
} else {
LoginFrame.this.reloadPanel.setMode(ReloadPanel.MODE_BLINK);
// Connection to server lost
// FIXME: autoreconnect after 10s
}
}
});
}
@Override
public void setAllowedCompanies(final List<Company> list) {
if (SwingUtilities.isEventDispatchThread()) {
throw new IllegalAccessError("Must not be called in EventDispatchThread");
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
LoginFrame.this.model.updateFrom(list);
if (!list.isEmpty()) {
LoginFrame.this.comboCompany.setEnabled(true);
for (Company company : list) {
// Reselect from saved company
if (company.getId() == LoginFrame.this.loginProperties.getLastCompanyId()) {
LoginFrame.this.comboCompany.setValue(company);
break;
}
}
if (LoginFrame.this.comboCompany.getSelectedItem() == null) {
LoginFrame.this.comboCompany.setSelectedIndex(0);
}
LoginFrame.this.buttonConnect.setEnabled(!LoginFrame.this.textLogin.getValue().trim().isEmpty() && LoginFrame.this.comboCompany.getSelectedItem() != null);
} else {
LoginFrame.this.buttonConnect.setEnabled(false);
}
LoginFrame.this.reloadPanel.setMode(ReloadPanel.MODE_EMPTY);
}
});
}
@Override
public void setEnabled(boolean b) {
if (b) {
this.reloadPanel.setMode(ReloadPanel.MODE_EMPTY);
}
this.textLogin.getComp().setEnabled(b);
this.textPassWord.setEnabled(b);
this.comboCompany.setEnabled(b);
this.saveCheckBox.setEnabled(b);
this.buttonConnect.setEnabled(b);
}
private final void initLocalization(final String baseName, final List<Locale> toDisplay) {
if (baseName == null)
throw new NullPointerException("Null baseName");
if (this.localeBaseName != null)
throw new IllegalStateException("Already inited to " + this.localeBaseName);
this.localeBaseName = baseName;
this.localesToDisplay.addAll(toDisplay);
// this.setUILanguage(UserProps.getInstance().getLocale());
TM.getInstance();
}
private void setUILanguage(Locale locale) {
System.err.println("LoginFrame.setUILanguage():" + this.localeBaseName + " locale:" + locale);
final ResourceBundle bundle = ResourceBundle.getBundle(this.localeBaseName, locale, TranslationManager.getControl());
this.loginLabel.setText(bundle.getString("loginLabel"));
this.passwordLabel.setText(bundle.getString("passwordLabel"));
this.companyLabel.setText(bundle.getString("companyLabel"));
this.saveCheckBox.setText(bundle.getString("saveCheckBox"));
this.buttonConnect.setText(bundle.getString("buttonConnect"));
this.langButton.setText(locale.getLanguage());
this.langButton.setVisible(true);
Locale.setDefault(locale);
}
private static MessageDigest md;
private static synchronized MessageDigest getMessageDigest() {
if (md == null)
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
// should be standard
throw new IllegalStateException("no SHA1", e);
}
return md;
}
public static String encodePassword(String clearPassword) {
final byte[] s;
synchronized (getMessageDigest()) {
getMessageDigest().reset();
getMessageDigest().update(clearPassword.getBytes());
s = getMessageDigest().digest();
}
return Base64.encodeBytes(s);
}
public String getCompany() {
return this.company;
}
public void saveAndDispose() throws IOException {
this.loginProperties.setLastCompanyId(this.comboCompany.getSelectedItem().getId());
this.loginProperties.setLastCompanyName(this.comboCompany.getSelectedItem().getName());
this.loginProperties.setLastLoginName(this.textLogin.getValue());
this.loginProperties.store();
dispose();
}
private void sendAllowedCompanyRequest(String login, String passwordHash) {
this.executorService.execute(new Runnable() {
@Override
public void run() {
LoginFrame.this.client.setCredentials(login, passwordHash);
final JSONObject params = new JSONObject();
params.put("login", login);
try {
JSONObject result = (JSONObject) LoginFrame.this.client.rpcCall(LoginFrame.this.serverUrl, "getAllowedCompanies", params);
JSONArray array = (JSONArray) result.get("companies");
List<Company> list = new ArrayList<>();
for (Object o : array) {
final Company c = new Company();
c.fromJSON((JSONObject) o);
list.add(c);
}
setAllowedCompanies(list);
} catch (IOException e) {
setAllowedCompanies(Collections.emptyList());
e.printStackTrace();
}
}
});
}
/**
* @param encodedPassword : Base64 encodded hash of the password
*/
private void sendLoginRequest(String login, String encodedPassword, int idCompany, final String context) {
this.executorService.execute(new Runnable() {
@Override
public void run() {
stateChanged(getConnectionState(), CONNECTION_INPROGRESS);
LoginFrame.this.client.setCredentials(login, encodedPassword);
final JSONObject params = new JSONObject();
params.put("company-id", idCompany);
params.put("context", context);
try {
JSONObject result = (JSONObject) LoginFrame.this.client.rpcCall(LoginFrame.this.serverUrl, "login", params);
if (result.getAsString("status").equals("granted")) {
LoginFrame.this.idCompany = idCompany;
stateChanged(getConnectionState(), CONNECTION_OK);
} else {
stateChanged(getConnectionState(), CONNECTION_REFUSED);
}
} catch (IOException e) {
stateChanged(getConnectionState(), CONNECTION_BROKEN);
}
}
});
}
public int getCompanyId() {
return this.idCompany;
}
}