/*
Copyright 2008-2013 Josh Drummond
This file is part of WebPasswordSafe.
WebPasswordSafe is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
WebPasswordSafe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with WebPasswordSafe; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.webpasswordsafe.client.ui;
import java.util.List;
import java.util.Set;
import net.webpasswordsafe.client.WebPasswordSafe;
import net.webpasswordsafe.client.i18n.TextMessages;
import net.webpasswordsafe.client.remote.PasswordService;
import net.webpasswordsafe.client.remote.UserService;
import net.webpasswordsafe.common.model.AccessLevel;
import net.webpasswordsafe.common.model.Password;
import net.webpasswordsafe.common.model.PasswordData;
import net.webpasswordsafe.common.model.Permission;
import net.webpasswordsafe.common.model.Subject;
import net.webpasswordsafe.common.model.Tag;
import net.webpasswordsafe.common.util.Constants;
import net.webpasswordsafe.common.util.Utils;
import com.extjs.gxt.ui.client.Style.HorizontalAlignment;
import com.extjs.gxt.ui.client.data.BaseModel;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.util.Format;
import com.extjs.gxt.ui.client.widget.Info;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.Window;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.form.CheckBox;
import com.extjs.gxt.ui.client.widget.form.ComboBox;
import com.extjs.gxt.ui.client.widget.form.NumberField;
import com.extjs.gxt.ui.client.widget.form.TextArea;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.extjs.gxt.ui.client.widget.layout.AbsoluteLayout;
import com.extjs.gxt.ui.client.widget.layout.AbsoluteData;
import com.extjs.gxt.ui.client.widget.form.LabelField;
/**
* @author Josh Drummond
*
*/
public class PasswordDialog extends Window implements PermissionListener
{
private final static TextMessages textMessages = GWT.create(TextMessages.class);
private Password password;
private TextField<String> nameTextBox;
private TextField<String> usernameTextBox;
private TextField<String> passwordTextBox;
private TextHelperComboBox<TagData> tagsComboBox;
private ListStore<TagData> tagStore;
private NumberField maxHistoryTextBox;
private TextArea notesTextArea;
private CheckBox activeCheckBox;
private TagLoadListener tagLoadListener;
public PasswordDialog(Password password, TagLoadListener tagLoadListener)
{
this.password = password;
this.tagLoadListener = tagLoadListener;
boolean isPasswordReadOnly = password.getMaxEffectiveAccessLevel().equals(AccessLevel.READ);
boolean isPasswordGrantable = password.getMaxEffectiveAccessLevel().equals(AccessLevel.GRANT);
this.setHeading(textMessages.password());
this.setModal(true);
this.setLayout(new AbsoluteLayout());
this.setSize(430, 415);
this.setResizable(false);
LabelField lblfldName = new LabelField(textMessages.title_());
add(lblfldName, new AbsoluteData(7, 6));
nameTextBox = new TextField<String>();
nameTextBox.setReadOnly(isPasswordReadOnly);
add(nameTextBox, new AbsoluteData(82, 6));
nameTextBox.setSize("331px", "22px");
LabelField lblfldUsername = new LabelField(textMessages.username_());
add(lblfldUsername, new AbsoluteData(7, 34));
usernameTextBox = new TextField<String>();
usernameTextBox.setReadOnly(isPasswordReadOnly);
add(usernameTextBox, new AbsoluteData(82, 34));
usernameTextBox.setSize("331px", "22px");
LabelField lblfldPassword = new LabelField(textMessages.password_());
add(lblfldPassword, new AbsoluteData(7, 62));
passwordTextBox = new TextField<String>();
passwordTextBox.setReadOnly(isPasswordReadOnly);
add(passwordTextBox, new AbsoluteData(82, 62));
passwordTextBox.setSize("331px", "22px");
Button generateButton = new Button(textMessages.generatePassword(), new SelectionListener<ButtonEvent>() {
@Override
public void componentSelected(ButtonEvent ce) {
doGeneratePassword();
}
});
generateButton.setEnabled(!isPasswordReadOnly);
add(generateButton, new AbsoluteData(82, 90));
generateButton.setSize("127px", "22px");
Button currentButton = new Button(textMessages.currentPassword(), new SelectionListener<ButtonEvent>()
{
@Override
public void componentSelected(ButtonEvent ce)
{
doGetCurrentPassword();
}
});
add(currentButton, new AbsoluteData(215, 90));
currentButton.setSize("127px", "22px");
currentButton.setEnabled(password.getId() > 0);
Button historyButton = new Button(textMessages.viewPasswordHistory(), new SelectionListener<ButtonEvent>()
{
@Override
public void componentSelected(ButtonEvent ce)
{
doViewPasswordHistory();
}
});
add(historyButton, new AbsoluteData(215, 311));
historyButton.setSize("127px", "22px");
historyButton.setEnabled(password.getId() > 0);
LabelField lblfldTags = new LabelField(textMessages.tags_());
add(lblfldTags, new AbsoluteData(6, 118));
tagsComboBox = new TextHelperComboBox<TagData>();
tagsComboBox.setReadOnly(isPasswordReadOnly);
add(tagsComboBox, new AbsoluteData(82, 118));
tagsComboBox.setSize("331px", "22px");
tagsComboBox.setEditable(true);
tagsComboBox.setForceSelection(false);
tagsComboBox.setHideTrigger(true);
tagStore = new ListStore<TagData>();
tagsComboBox.setStore(tagStore);
tagsComboBox.setDisplayField(Constants.NAME);
LabelField lblfldNotes = new LabelField(textMessages.notes_());
add(lblfldNotes, new AbsoluteData(6, 146));
notesTextArea = new TextArea();
notesTextArea.setReadOnly(isPasswordReadOnly);
add(notesTextArea, new AbsoluteData(82, 146));
notesTextArea.setSize("331px", "75px");
LabelField lblfldMaxHistory = new LabelField(textMessages.maxHistory_());
add(lblfldMaxHistory, new AbsoluteData(6, 227));
maxHistoryTextBox = new NumberField();
maxHistoryTextBox.setReadOnly(isPasswordReadOnly);
maxHistoryTextBox.setPropertyEditorType(Integer.class);
add(maxHistoryTextBox, new AbsoluteData(82, 227));
maxHistoryTextBox.setSize("76px", "22px");
LabelField lblfldInfinite = new LabelField(textMessages.infinite());
add(lblfldInfinite, new AbsoluteData(164, 227));
activeCheckBox = new CheckBox();
activeCheckBox.setReadOnly(isPasswordReadOnly);
activeCheckBox.setBoxLabel(textMessages.active());
add(activeCheckBox, new AbsoluteData(82, 255));
Button editPermissionsButton = new Button(isPasswordGrantable ?
textMessages.editPermissions() : textMessages.viewPermissions(),
new SelectionListener<ButtonEvent>()
{
@Override
public void componentSelected(ButtonEvent ce) {
doEditPermissions();
}
});
add(editPermissionsButton, new AbsoluteData(82, 283));
editPermissionsButton.setSize("260px", "22px");
Button accessAuditButton = new Button(textMessages.viewAccessAuditLog(),
new SelectionListener<ButtonEvent>()
{
@Override
public void componentSelected(ButtonEvent ce)
{
doViewAccessAuditLog();
}
});
add(accessAuditButton, new AbsoluteData(82, 311));
accessAuditButton.setSize("127px", "22px");
accessAuditButton.setEnabled(password.getId() > 0);
Button saveButton = new Button(textMessages.save(), new SelectionListener<ButtonEvent>() {
@Override
public void componentSelected(ButtonEvent ce) {
doSave();
}
});
saveButton.setEnabled(!isPasswordReadOnly);
Button cancelButton = new Button(textMessages.cancel(), new SelectionListener<ButtonEvent>() {
@Override
public void componentSelected(ButtonEvent ce) {
doCancel();
}
});
setButtonAlign(HorizontalAlignment.CENTER);
addButton(saveButton);
addButton(cancelButton);
doLoadTags();
}
@Override
public void show()
{
super.show();
setFields();
setFocusWidget(nameTextBox);
}
private void doViewPasswordHistory()
{
new PasswordHistoryDialog(password).show();
}
private void doViewAccessAuditLog()
{
new PasswordAccessAuditDialog(password).show();
}
private void doGetCurrentPassword()
{
AsyncCallback<String> callback = new AsyncCallback<String>()
{
@Override
public void onFailure(Throwable caught)
{
WebPasswordSafe.handleServerFailure(caught);
}
@Override
public void onSuccess(String result)
{
passwordTextBox.setValue(result);
}
};
PasswordService.Util.getInstance().getCurrentPassword(password.getId(), callback);
}
private void doGeneratePassword()
{
AsyncCallback<String> callback = new AsyncCallback<String>()
{
@Override
public void onFailure(Throwable caught)
{
WebPasswordSafe.handleServerFailure(caught);
}
@Override
public void onSuccess(String result)
{
passwordTextBox.setValue(result);
}
};
PasswordService.Util.getInstance().generatePassword(callback);
}
private void doSave()
{
if (validateFields())
{
password.setName(Utils.safeString(nameTextBox.getValue()));
password.setUsername(Utils.safeString(usernameTextBox.getValue()));
password.removePasswordData();
PasswordData passwordDataItem = new PasswordData();
passwordDataItem.setPassword(Utils.safeString(passwordTextBox.getValue()));
password.addPasswordData(passwordDataItem);
password.addTagsAsString(Utils.safeString(tagsComboBox.getRawValue()));
password.setNotes(Utils.safeString(notesTextArea.getValue()));
password.setMaxHistory(Utils.safeInt(String.valueOf(maxHistoryTextBox.getValue())));
password.setMaxHistory((password.getMaxHistory() < -1) ? -1 : password.getMaxHistory());
password.setActive(activeCheckBox.getValue());
final AsyncCallback<Boolean> callbackCheck = new AsyncCallback<Boolean>()
{
@Override
public void onFailure(Throwable caught)
{
WebPasswordSafe.handleServerFailure(caught);
}
@Override
public void onSuccess(Boolean result)
{
// true => password title already taken, else go ahead and save
if (result)
{
MessageBox.alert(textMessages.error(), textMessages.passwordTitleExists(), null);
}
else
{
final AsyncCallback<Void> callback = new AsyncCallback<Void>()
{
@Override
public void onFailure(Throwable caught)
{
WebPasswordSafe.handleServerFailure(caught);
}
@Override
public void onSuccess(Void result)
{
Info.display(textMessages.status(), textMessages.passwordSaved());
tagLoadListener.reloadTags();
hide();
}
};
if (password.getId() == 0)
{
PasswordService.Util.getInstance().addPassword(password, callback);
}
else
{
PasswordService.Util.getInstance().updatePassword(password, callback);
}
}
}
};
PasswordService.Util.getInstance().isPasswordTaken(password.getName(), password.getUsername(), password.getId(), callbackCheck);
}
}
private boolean validateFields()
{
if (Utils.safeString(nameTextBox.getValue()).equals(""))
{
MessageBox.alert(textMessages.error(), textMessages.mustEnterTitle(), null);
return false;
}
if (Utils.safeString(nameTextBox.getValue()).length() > Password.LENGTH_NAME)
{
MessageBox.alert(textMessages.error(), textMessages.tooLongTitle(), null);
return false;
}
if (Utils.safeString(usernameTextBox.getValue()).equals(""))
{
MessageBox.alert(textMessages.error(), textMessages.mustEnterUsername(), null);
return false;
}
if (Utils.safeString(usernameTextBox.getValue()).length() > Password.LENGTH_USERNAME)
{
MessageBox.alert(textMessages.error(), textMessages.tooLongUsername(), null);
return false;
}
if ((password.getId() < 1) && (Utils.safeString(passwordTextBox.getValue()).equals("")))
{
MessageBox.alert(textMessages.error(), textMessages.mustEnterPassword(), null);
return false;
}
if (Utils.safeString(passwordTextBox.getValue()).length() > PasswordData.LENGTH_PASSWORD)
{
MessageBox.alert(textMessages.error(), textMessages.tooLongPassword(), null);
return false;
}
String[] tagNames = Utils.safeString(tagsComboBox.getRawValue()).replaceAll(",", " ").split(" ");
for (String tagName : tagNames)
{
tagName = tagName.trim();
if (!"".equals(tagName))
{
if (tagName.length() > Tag.LENGTH_NAME)
{
MessageBox.alert(textMessages.error(), textMessages.tooLongTag(), null);
return false;
}
}
}
return true;
}
private void doEditPermissions()
{
AsyncCallback<List<Subject>> callback = new AsyncCallback<List<Subject>>()
{
@Override
public void onFailure(Throwable caught)
{
WebPasswordSafe.handleServerFailure(caught);
}
@Override
public void onSuccess(List<Subject> result)
{
doShowPermissionDialog(result);
}
};
UserService.Util.getInstance().getSubjects(true, callback);
}
private void doShowPermissionDialog(List<Subject> subjects)
{
new PermissionDialog(this, password, subjects).show();
}
private void setFields()
{
nameTextBox.setValue(password.getName());
usernameTextBox.setValue(password.getUsername());
tagsComboBox.setRawValue(password.getTagsAsString());
notesTextArea.setValue(password.getNotes());
activeCheckBox.setValue(password.isActive());
maxHistoryTextBox.setValue(password.getMaxHistory());
}
private void doCancel()
{
hide();
}
/* (non-Javadoc)
* @see net.webpasswordsafe.client.ui.PermissionListener#doPermissionsChanged(java.util.List)
*/
@Override
public void doPermissionsChanged(Set<Permission> permissions)
{
password.clearPermissions();
for (Permission permission : permissions)
{
password.addPermission(permission);
}
}
private void doLoadTags()
{
AsyncCallback<List<Tag>> callback = new AsyncCallback<List<Tag>>()
{
@Override
public void onFailure(Throwable caught)
{
WebPasswordSafe.handleServerFailure(caught);
}
@Override
public void onSuccess(List<Tag> result)
{
refreshTags(result);
}
};
PasswordService.Util.getInstance().getAllTags(callback);
}
private void refreshTags(List<Tag> tags)
{
tagStore.removeAll();
for (Tag tag : tags)
{
tagStore.add(new TagData(tag));
}
}
private class TagData extends BaseModel
{
private static final long serialVersionUID = 1L;
public TagData(Tag tag)
{
set(Constants.ID, tag.getId());
set(Constants.NAME, Format.htmlEncode(tag.getName()));
set(Constants.TAG, tag);
}
}
private class TextHelperComboBox<D extends ModelData> extends ComboBox<D>
{
@Override
public void doQuery(String q, boolean forceAll)
{
if (q == null)
{
q = "";
}
else if (q.endsWith(" "))
{
q = "";
}
else
{
q = q.replaceAll(",", " ");
int space = q.lastIndexOf(" ");
if ((space >= 0) && (space < q.length()))
{
q = q.substring(space+1);
}
}
super.doQuery(q, forceAll);
}
@Override
public void setValue(D value)
{
String s = getRawValue();
String sf = s.replaceAll(",", " ");
if (!"".equals(sf) && !sf.endsWith(" "))
{
//then we need to chop off the partial typed value we are replacing
int space = sf.lastIndexOf(" ");
if (space >= 0)
{
s = s.substring(0, space+1);
}
else
{
s = "";
}
}
s += value.get(Constants.NAME);
super.setRawValue(s);
}
@Override
public void selectAll()
{
// override to disable text selectall during popup
}
}
}