ca.sqlpower.architect.swingui.enterprise.SecurityPanel.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.architect.swingui.enterprise.SecurityPanel.java

Source

/*
 * Copyright (c) 2010, SQL Power Group Inc.
 *
 * This file is part of Power*Architect.
 *
 * Power*Architect 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 3 of the License, or
 * (at your option) any later version.
 *
 * Power*Architect 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 this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

package ca.sqlpower.architect.swingui.enterprise;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.NoSuchElementException;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

import org.apache.commons.codec.binary.Hex;

import ca.sqlpower.architect.ArchitectSession;
import ca.sqlpower.architect.enterprise.ArchitectClientSideSession;
import ca.sqlpower.architect.enterprise.DomainCategory;
import ca.sqlpower.architect.swingui.ArchitectSwingProject;
import ca.sqlpower.enterprise.AbstractNetworkConflictResolver;
import ca.sqlpower.enterprise.AbstractNetworkConflictResolver.UpdateListener;
import ca.sqlpower.enterprise.client.Grant;
import ca.sqlpower.enterprise.client.Group;
import ca.sqlpower.enterprise.client.SPServerInfo;
import ca.sqlpower.enterprise.client.User;
import ca.sqlpower.object.SPObject;
import ca.sqlpower.swingui.DataEntryPanel;
import ca.sqlpower.swingui.SPSUtils;

import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;

/**
 * Shows the users and groups in the system workspace and allows them to be
 * edited as well as adding and removing groups and users.
 */
public class SecurityPanel {

    private static final ImageIcon USER_ICON = new ImageIcon(SecurityPanel.class.getResource("icons/user.png"));
    private static final ImageIcon GROUP_ICON = new ImageIcon(SecurityPanel.class.getResource("icons/group.png"));

    private final Action closeAction;

    private final JTree tree;
    private final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Security");
    private final DefaultMutableTreeNode usersNode = new DefaultMutableTreeNode("Users");
    private final DefaultMutableTreeNode groupsNode = new DefaultMutableTreeNode("Groups");

    /**
     * The main panel of this window and can be added to a dialog or other panel
     * to let the user modify the security settings.
     */
    private final JPanel panel;

    /**
     * Holds a tree of editable objects on the left and the editor for the
     * currently selected item on the right.
     */
    private final JSplitPane splitpane;

    private final JPanel rightSidePanel;
    private final JScrollPane treePane;

    private final ArchitectSwingProject securityWorkspace;

    private final String username;

    private DataEntryPanel currentGroupOrUserEditPanel;
    private DefaultMutableTreeNode selectionForEditors;

    private final TreeSelectionListener treeListener = new TreeSelectionListener() {
        public void valueChanged(TreeSelectionEvent e) {
            DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
            if (dmtn != null && dmtn.isLeaf() && dmtn != groupsNode && dmtn != usersNode) {
                if (promptForUnsavedChanges()) {
                    createEditPanel((SPObject) dmtn.getUserObject());
                    panel.revalidate();
                    selectionForEditors = dmtn;
                } else {
                    if (selectionForEditors != null) {
                        tree.removeTreeSelectionListener(treeListener);
                        tree.setSelectionPath(new TreePath(selectionForEditors.getPath()));
                        tree.addTreeSelectionListener(treeListener);
                    }
                }
            }
        }
    };

    private final MouseListener popupListener = new MouseListener() {
        public void mouseReleased(MouseEvent e) {
            maybeShowPopup(e);
        }

        public void mousePressed(MouseEvent e) {
            maybeShowPopup(e);
        }

        public void mouseClicked(MouseEvent e) {
            maybeShowPopup(e);
        }

        public void mouseEntered(MouseEvent e) {
            maybeShowPopup(e);
        }

        public void mouseExited(MouseEvent e) {
            maybeShowPopup(e);
        }

        private void maybeShowPopup(MouseEvent e) {
            if (e.isPopupTrigger()) {

                final TreePath path = tree.getPathForLocation(e.getX(), e.getY());

                DefaultMutableTreeNode node;
                try {
                    node = (DefaultMutableTreeNode) path.getLastPathComponent();
                    tree.setSelectionPath(path);
                } catch (Exception ex) {
                    node = null;
                }

                JPopupMenu popupMenu = new JPopupMenu();
                if (node != null) {
                    SPObject obj;
                    try {
                        obj = (SPObject) node.getUserObject();
                    } catch (Exception ex) {
                        obj = null;
                    }
                    popupMenu.add(new EditAction(path));
                    popupMenu.add(new EditSecuritySettingsAction(path));
                    if (obj != null)
                        popupMenu.add(new DeleteAction(obj));
                    popupMenu.addSeparator();
                }

                popupMenu.add(newUserAction);
                popupMenu.add(newGroupAction);
                popupMenu.show(e.getComponent(), e.getX(), e.getY());
            }
        }
    };

    private final Action newUserAction = new AbstractAction("New User...") {
        public void actionPerformed(ActionEvent e) {
            if (promptForUnsavedChanges()) {
                User user = createUserFromPrompter();
                if (user != null) {
                    try {
                        securityWorkspace.begin("Creating a new user");
                        securityWorkspace.addChild(user, securityWorkspace.getChildren(User.class).size());
                        DomainCategory category = new DomainCategory(user.getName() + "'s Domains");
                        user.addGrant(new Grant(category, true, true, true, true, true));
                        securityWorkspace.addChild(category,
                                securityWorkspace.getChildren(DomainCategory.class).size());
                        securityWorkspace.commit();
                    } catch (Exception ex) {
                        securityWorkspace.rollback(ex.getMessage());
                        throw new RuntimeException(ex);
                    }

                    refreshTree();
                    Enumeration<DefaultMutableTreeNode> userNodes = usersNode.children();
                    while (userNodes.hasMoreElements()) {
                        DefaultMutableTreeNode dmtn = userNodes.nextElement();
                        if (((User) dmtn.getUserObject()).getUUID().equals(user.getUUID())) {
                            tree.setSelectionPath(new TreePath(dmtn.getPath()));
                        }
                    }
                }
            }
        }
    };

    private final Action newGroupAction = new AbstractAction("New Group...") {
        public void actionPerformed(ActionEvent e) {
            if (promptForUnsavedChanges()) {
                Group group = createGroupFromPrompter();
                if (group != null) {
                    securityWorkspace.addChild(group, securityWorkspace.getChildren(Group.class).size());
                    refreshTree();
                    Enumeration<DefaultMutableTreeNode> userNodes = groupsNode.children();
                    while (userNodes.hasMoreElements()) {
                        DefaultMutableTreeNode dmtn = userNodes.nextElement();
                        if (((Group) dmtn.getUserObject()).getUUID().equals(group.getUUID())) {
                            tree.setSelectionPath(new TreePath(dmtn.getPath()));
                        }
                    }
                }
            }
        }
    };

    private final MessageDigest digester;
    private final Dialog dialog;
    private final ArchitectSession session;

    public SecurityPanel(SPServerInfo serverInfo, Action closeAction, Dialog d, ArchitectSession session) {
        this.closeAction = closeAction;

        splitpane = new JSplitPane();
        panel = new JPanel();

        ArchitectClientSideSession clientSideSession = ArchitectClientSideSession.getSecuritySessions()
                .get(serverInfo.getServerAddress());

        //Displaying an indeterminate progress bar in place of the split pane
        //until the security session has loaded fully.
        if (clientSideSession.getUpdater().getRevision() <= 0) {
            JLabel messageLabel = new JLabel("Opening");
            JProgressBar progressBar = new JProgressBar();
            progressBar.setIndeterminate(true);

            DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("pref:grow, 5dlu, pref"));
            builder.setDefaultDialogBorder();
            builder.append(messageLabel, 3);
            builder.nextLine();
            builder.append(progressBar, 3);

            UpdateListener l = new UpdateListener() {

                @Override
                public void workspaceDeleted() {
                    //do nothing
                }

                @Override
                public boolean updatePerformed(AbstractNetworkConflictResolver resolver) {
                    panel.removeAll();
                    panel.add(splitpane);
                    dialog.pack();
                    refreshTree();
                    return true;
                }

                @Override
                public boolean updateException(AbstractNetworkConflictResolver resolver, Throwable t) {
                    //do nothing, the error will be handled elsewhere
                    return true;
                }

                @Override
                public void preUpdatePerformed(AbstractNetworkConflictResolver resolver) {
                    //do nothing
                }
            };

            clientSideSession.getUpdater().addListener(l);
            panel.add(builder.getPanel());
        }
        this.securityWorkspace = clientSideSession.getWorkspace();
        this.username = serverInfo.getUsername();
        this.dialog = d;
        this.session = session;

        try {
            digester = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }

        rootNode.add(usersNode);
        rootNode.add(groupsNode);

        rightSidePanel = new JPanel();

        tree = new JTree(rootNode);
        tree.addTreeSelectionListener(treeListener);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        tree.setCellRenderer(new DefaultTreeCellRenderer() {
            public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
                    boolean leaf, int row, boolean hasFocus) {
                super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
                Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
                if (userObject instanceof User) {
                    setIcon(USER_ICON);
                } else if (userObject instanceof Group) {
                    setIcon(GROUP_ICON);
                }
                return this;
            }
        });

        treePane = new JScrollPane(tree);
        treePane.setPreferredSize(new Dimension(200, treePane.getPreferredSize().height));

        tree.addMouseListener(popupListener);

        splitpane.setRightComponent(rightSidePanel);
        splitpane.setLeftComponent(treePane);

        if (clientSideSession.getUpdater().getRevision() > 0) {
            panel.removeAll();
            panel.add(splitpane);
        }

        refreshTree();

        try {
            tree.setSelectionPath(new TreePath(usersNode.getFirstChild()));
        } catch (NoSuchElementException e) {
        } // This just means that the node has no children, so we cannot expand the path.
    }

    private void refreshTree() {
        tree.setModel(new DefaultTreeModel(rootNode));
        usersNode.removeAllChildren();
        for (User user : securityWorkspace.getChildren(User.class)) {
            usersNode.add(new DefaultMutableTreeNode(user));
        }
        groupsNode.removeAllChildren();
        for (Group group : securityWorkspace.getChildren(Group.class)) {
            groupsNode.add(new DefaultMutableTreeNode(group));
        }
        tree.expandPath(new TreePath(usersNode.getPath()));
        tree.expandPath(new TreePath(groupsNode.getPath()));
    }

    private void createEditPanel(final SPObject groupOrUser) {
        DataEntryPanel groupOrUserEditPanel;
        if (groupOrUser instanceof Group) {
            groupOrUserEditPanel = new GroupEditorPanel((Group) groupOrUser, username, closeAction);
        } else if (groupOrUser instanceof User) {
            groupOrUserEditPanel = new UserEditorPanel((User) groupOrUser, username, closeAction, session);
        } else {
            throw new IllegalStateException("Argument must be instance of Group or User");
        }

        currentGroupOrUserEditPanel = groupOrUserEditPanel;

        rightSidePanel.removeAll();
        rightSidePanel.add(groupOrUserEditPanel.getPanel());

        panel.revalidate();
        dialog.pack();
    }

    private User createUserFromPrompter() {
        JTextField nameField = new JTextField(15);
        JTextField passField = new JPasswordField(15);
        JTextField confirmField = new JPasswordField(15);

        JPanel namePanel = new JPanel(new BorderLayout());
        namePanel.add(new JLabel("User Name"), BorderLayout.WEST);
        namePanel.add(nameField, BorderLayout.EAST);

        JPanel passPanel = new JPanel(new BorderLayout());
        passPanel.add(new JLabel("Password"), BorderLayout.WEST);
        passPanel.add(passField, BorderLayout.EAST);

        JPanel confirmPanel = new JPanel(new BorderLayout());
        confirmPanel.add(new JLabel("Confirm Password"), BorderLayout.WEST);
        confirmPanel.add(confirmField, BorderLayout.EAST);

        Object[] messages = new Object[] { "Specify the User's Name and Password.", namePanel, passPanel,
                confirmPanel };

        String[] options = { "OK", "Cancel", };
        int option = JOptionPane.showOptionDialog(getPanel(), messages, "Specify the User's Name and Password",
                JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, options, options[0]);

        if (nameField.getText().equals("") || nameField.getText() == null || passField.getText().equals("")
                || passField.getText() == null) {
            return null;
        }

        if (!passField.getText().equals(confirmField.getText())) {
            JOptionPane.showMessageDialog(getPanel(), "The passwords you entered do not match.", "Error",
                    JOptionPane.ERROR_MESSAGE);
            return null;
        }

        User user = null;
        if (option == 0) {
            String password;
            try {
                password = new String(Hex.encodeHex(digester.digest(passField.getText().getBytes("UTF-8"))));
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("Unable to encode password", e);
            }
            user = new User(nameField.getText(), password);
        }

        return user;
    }

    private Group createGroupFromPrompter() {
        Object input = JOptionPane.showInputDialog(getPanel(), "Enter a name for the group.",
                "Enter the name of the group.", JOptionPane.INFORMATION_MESSAGE);
        if (input != null && !input.equals("")) {
            return new Group((String) input);
        } else {
            return null;
        }
    }

    private boolean promptForUnsavedChanges() {
        if (currentGroupOrUserEditPanel != null && (currentGroupOrUserEditPanel.hasUnsavedChanges())) {

            int option = JOptionPane.showConfirmDialog(getPanel(),
                    "You have not saved all of your changes,\n" + "do you want to save them now?", "",
                    JOptionPane.INFORMATION_MESSAGE);

            if (option == JOptionPane.YES_OPTION) {
                currentGroupOrUserEditPanel.applyChanges();
                return true;
            }

            if (option == JOptionPane.NO_OPTION) {
                return true;
            }

            if (option == JOptionPane.CANCEL_OPTION) {
                return false;
            }
        }

        return true;
    }

    private boolean promptForDelete(SPObject obj) {

        String typeString = "";
        if (obj instanceof User) {
            typeString = "User";
        }
        if (obj instanceof Group) {
            typeString = "Group";
        }

        Object[] options = { "Yes", "No" };

        int option = JOptionPane.showOptionDialog(null,
                new Object[] {
                        "Are you sure you want to delete the " + typeString + " \"" + obj.getName() + "\"?" },
                "", JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, options, options[0]);

        if (option == 0) {
            return true;
        }

        return false;
    }

    public JPanel getPanel() {
        return panel;
    }

    private class EditAction extends AbstractAction {
        private final TreePath path;

        public EditAction(TreePath path) {
            super("Edit");
            this.path = path;
        }

        public void actionPerformed(ActionEvent e) {
            tree.setSelectionPath(path);
        }
    }

    private class EditSecuritySettingsAction extends AbstractAction {
        private final TreePath path;

        public EditSecuritySettingsAction(TreePath path) {
            super("Manage Security Settings...");
            this.path = path;
        }

        public void actionPerformed(ActionEvent e) {

            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
            SPObject object;

            try {
                object = (SPObject) node.getUserObject();
            } catch (Exception ex) {
                object = null;
            }

            Class<?> objectClass = null;
            if (object != null) {
                objectClass = object.getClass();
            } else {
                if (node == usersNode) {
                    objectClass = User.class;
                } else if (node == groupsNode) {
                    objectClass = Group.class;
                }
            }

            final JDialog d = SPSUtils.makeOwnedDialog(dialog, "Security Manager");

            Action closeAction = new AbstractAction("Close") {
                public void actionPerformed(ActionEvent e) {
                    d.dispose();
                }
            };

            ProjectSecurityPanel spm = new ProjectSecurityPanel(securityWorkspace, object, objectClass, username, d,
                    closeAction);
            d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            d.setContentPane(spm.getPanel());

            SPSUtils.makeJDialogCancellable(d, null);
            d.pack();
            d.setLocationRelativeTo(panel);
            d.setVisible(true);
        }
    }

    private class DeleteAction extends AbstractAction {
        private final SPObject obj;

        public DeleteAction(SPObject obj) {
            super("Delete");
            this.obj = obj;
        }

        public void actionPerformed(ActionEvent e) {
            if (promptForUnsavedChanges()) {
                if (promptForDelete(obj)) {
                    try {
                        if (obj instanceof User) {
                            for (Group group : securityWorkspace.getChildren(Group.class)) {
                                group.removeUser((User) obj);
                            }
                        }
                        securityWorkspace.removeChild(obj);
                    } catch (Exception ex) {
                        throw new RuntimeException("Unable to delete: ", ex);
                    }
                    refreshTree();
                    tree.setSelectionPath(new TreePath(usersNode.getFirstChild()));
                }
            }
        }
    }
}