0

I have been facing the above mentioned problem in my java application that I recently created. Even though I clearly set the field as JPasswordField and have tried to mask the password with astericks, I continue to face this issue. I issue does not occur when we edit the password field it occurs only when you select the row. For example I have 2 columns in a row and if I select the entire row and try to copy paste the row in notepad the pasword appears. I'm new in the world of java programming, if someone can help will be of great help.

  • 6
    What do you mean by "2 columns in a row"? Can you please post a [SSCCE](http://pscode.org/sscce.html). – Howard Jan 08 '12 at 10:22
  • Well, the masking is only done in the display, the data behind it is still the plain password. Why do you display it anyways? Either just leave it out or replace the text with asterisks when setting the cell's value. – Thomas Jan 08 '12 at 10:26
  • Hi howard, assume you have a table containing 3 columns and 1 row. On selecting the entire row i.e. all 3 columns and copy pasting it to a notepad it reveals the password, this is when I'm displaying asterisks instead of the password. – user1136929 Jan 08 '12 at 10:34
  • 1
    @user1136929 Again, what do you mean by "table"? Is it a `JTable`? How did you get your `JPasswordField` into the table? In order to give you an answer to your question we need the SSCCE. – Howard Jan 08 '12 at 10:49

4 Answers4

3

What you want is rather logical. When copy-pasting the data from your table the rendering should be respected.

The standard copy action will use the data available in your model, where the password is available as plain text. We could start a discussion whether you want to have your model contain a password as plain text, without any hashing applied to it ... but that does not answer your question.

For your question you should modify the behavior of the cut/copy actions of the JTable. Take a look at the Drag and drop Swing tutorial, and more specifically to the Adding Cut, Copy and Paste section. Unfortunately I do not immediately find an example to refer to.

Edit

Find below an example of a JXTable which uses the rendered values for the copy-action (I did not copy-pasted the imports). A little side-note about the code:

  1. It uses SwingX to illustrate something for kleopatra as well
  2. The example TableModel and its elements are rather stupid. To avoid too much work I needed something similar to our real product to be able to copy some of the code
  3. The SpeedStringValue and AltitudeStringValue classes violate the StringValue interface as they return null. I was too lazy to define a new interface, and the StringValue instance I set on the SwingX DefaultTableRenderer behaves according to the documentation. I think however that having separate StringValue instances which each have knowledge for converting a specific class to String is a real-world use-case which is lacking in SwingX
  4. The TransferHandler reuses the StringValue logic to create a Table containing only String instances, and then falls back on the default JTable behavior. This allows to reuse the logic implemented in the renderer, and allows to copy the visual values instead of the model values. I am not sure whether this is the nicest solution, but it works. It would be nice however if similar behavior was standard in SwingX, as they already have the infrastructure
  5. The code lacks comments as it was already long enough. If something is unclear, leave a comment and I will try to clarify

    public class TableTransferHandlerDemo {
    
      public static void main( String[] args ) throws InvocationTargetException, InterruptedException {
        EventQueue.invokeAndWait( new Runnable() {
          public void run() {
            JFrame frame = new JFrame( "TestFrame" );
    
            JPanel contentPane = new JPanel( new BorderLayout(  ) );
            contentPane.add( createTable(), BorderLayout.CENTER );
            frame.getContentPane().add( contentPane );
    
            frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
            frame.pack();
            frame.setVisible( true );
          }
        } );
      }
    
      private static CompositeStringValue createStringValue() {
        CompositeStringValue stringValue = new CompositeStringValue();
        stringValue.delegates.add( new AltitudeStringValue() );
        stringValue.delegates.add( new SpeedStringValue() );
        return stringValue;
      }
    
      public static JXTable createTable(){
        final JXTable table = new JXTable(  );
        table.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
        table.setModel( createTableModel() );
        CompositeStringValue stringValue = createStringValue();
        table.setDefaultRenderer( Object.class, new DefaultTableRenderer( stringValue ) );
        table.setTransferHandler( new TableTransferHandler( table, stringValue ) );
        //make sure ctrl-c triggers a copy
        InputMap inputMap = table.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
        inputMap.put( KeyStroke.getKeyStroke( KeyEvent.VK_C, InputEvent.CTRL_MASK ), "copyAction" );
        table.getActionMap().put( "copyAction", new AbstractAction() {
          public void actionPerformed( ActionEvent e ) {
            ActionEvent event = new ActionEvent( table, e.getID(), e.getActionCommand(), e.getWhen(), e.getModifiers() );
            TransferHandler.getCopyAction().actionPerformed( event );
          }
        } );
        return table;
      }
    
      public static class Speed{
        public double speed;
        public String unit = "km/h";
        public Speed( double speed ){ this.speed = speed;}
      }
      public static class Altitude{
        public double altitude;
        public String unit = "m";
        public Altitude( double altitude ){ this.altitude = altitude; }
      }
      public static class SpeedStringValue implements StringValue{
        public String getString( Object o ) {
          if ( o instanceof Speed ){
            return ( ( Speed ) o ).speed + ( ( Speed ) o ).unit;
          }
          return null;
        }
      }
      public static class AltitudeStringValue implements StringValue{
        public String getString( Object o ) {
          if ( o instanceof Altitude ){
            return ( ( Altitude ) o ).altitude + ( ( Altitude ) o ).unit;
          }
          return null;
        }
      }
      public static class CompositeStringValue implements StringValue{
        public List<StringValue> delegates = new ArrayList<StringValue>(  );
        public String getString( Object o ) {
          for ( StringValue stringValue : delegates ) {
            String string = stringValue.getString( o );
            if ( string != null ) return string;
          }
          return o != null ? o.toString() : "null";
        }
      }
      public static TableModel createTableModel(){
        return new DefaultTableModel(
            new Object[][]{ new Object[]{ new Speed( 10 ), new Altitude( 100 )},
                new Object[]{ new Speed( 20 ), new Altitude( 200 ) }},
            new Object[]{"Speed", "Altitude"} );
      }
      public static class TableTransferHandler extends TransferHandler{
        private JXTable table;
        private StringValue stringValue;
    
        public TableTransferHandler( JXTable aTable, StringValue aStringValue ) {
          table = aTable;
          stringValue = aStringValue;
        }
        @Override
        public void exportToClipboard( JComponent aComponent, Clipboard aClipboard, int aAction ) throws IllegalStateException {
          JTable table = createTable();
          table.getTransferHandler().exportToClipboard( table, aClipboard, aAction );
        }
        @Override
        public void exportAsDrag( JComponent aComponent, InputEvent aEvent, int aAction ) {
          JTable table = createTable();
          table.getTransferHandler().exportAsDrag( table, aEvent, aAction );
        }
        @Override
        protected Transferable createTransferable( JComponent c ) {
          //this transfer handler should not create any transferables
          return null;
        }
        /**
         * Create a table, representing the JXTable containing only Strings
         */
        private JTable createTable() {
          JTable table = new JTable( new StringTableModel( this.table, stringValue ) );
          table.setSelectionModel( this.table.getSelectionModel() );//make sure the selection is synced
          return table;
        }
      }
    
      private static class StringTableModel extends AbstractTableModel {
        private JXTable delegateTable;
        private StringValue stringValue;
    
        private StringTableModel( JXTable aTable, StringValue aStringValue ) {
          delegateTable = aTable;
          stringValue = aStringValue;
        }
    
        public int getRowCount() {
          return delegateTable.getModel().getRowCount();
        }
    
        public int getColumnCount() {
          return delegateTable.getModel().getColumnCount();
        }
    
        public Object getValueAt( int aRowIndex, int aColumnIndex ) {
          return stringValue.getString( delegateTable.getValueAt( aRowIndex, aColumnIndex ) );
        }
      }
    }
    
Robin
  • 36,233
  • 5
  • 47
  • 99
  • Hi Robin u r ryt that I'm looking for a conceptual answer to the problem. I dont want to disable the copy paste functionality for other rows atleast in the JTable. Moreover I'm not sure why is this happening. I have replaced the string with asterisks by over writing the Renderer. Even though it shows asterisks but on copying it to clipboard it shows in text with correct value. – user1136929 Jan 08 '12 at 11:16
  • Like I said, you want the rendered values to be used during the copy action, which is not the default behavior. The default behavior works on the model – Robin Jan 08 '12 at 11:18
  • interesting approach :-) On a very first glimpse: looks like you are mixing view and model coordinates in the wrapping model: row/columnCount query the model, getValueAt queries the table. Best to stick to the table for all (c&p is about the visible rows/columns anyway) Actually, you don't need the explicit stringValue - instead of delegateTable.getValueAt(...) return a delegateTable.getStringAt(...). Will give it a closer look soon! – kleopatra Jan 10 '12 at 15:40
  • @kleopatra I might have made a few mistakes when converting the code to an SSCCE, but I will certainly check the real code. Just did not want to spend too much of my working time on this. – Robin Jan 10 '12 at 15:45
  • updated my answer with some code and a todo issue in SwingX, thanks for contributing :-) – kleopatra Jan 11 '12 at 12:55
  • @kleopatra thanks for adding the todo in SwingX. The more that works out of the box, the less code we have to maintain. Out of curiosity, can anybody contribute to the SwingX project ? – Robin Jan 11 '12 at 21:46
2

Playing guessing games as everybody else (the description is ... lacking ;-)

So assuming a two-column tableModel containing username and password, respectively, rendered in a JTable with drag-enabled to true and default transferHandler. Assuming the rendering of the password in the JTable is "masked" somehow but appears as clear-text in c&p.

@Robin already detected the underlying reason: the default transferHandler simply uses getValueAt(...).toString() to create the transferable. Which leads to revealing the password string, it that's what is stored in the model.

A simple way out (as opposed to a better Transferhandler, which again @Robin already mentioned: uses the rendered value instead of the toString. Note to myself: file task for SwingX) for is to not store the plain password but a wrapper object:

public class Password {
    private final String password;

    public Password(String password) {
       this.password = password;
    }

    // api as needed to make it worthwile ...

    public boolean isValid(String password) {
        ...
    }

    // for the sake of c&p, override the toString
    // for the lazy, this is the string rep used by the default renderer
    @Override
    public String toString() {
        return "******************";
    }
}

Addendum (this is SwingX specific, extending my comment to @Robin's example)

Actually I like the approach as a quick solution for copying. Just "fixing" the wrapping model to make full use of current api, that is to use table.getStringAt(...) for the String representation. With that, there is no need to pass the StringValue at several places, internals will handle it as appropriate.

private static class StringTableModel extends AbstractTableModel {
    private JXTable delegateTable;

    private StringTableModel(JXTable aTable) {
        delegateTable = aTable;
    }

    @Override
    public int getRowCount() {
        return delegateTable.getRowCount();
    }

    @Override
    public int getColumnCount() {
        return delegateTable.getColumnCount();
    }

    @Override
    public Object getValueAt(int aRowIndex, int aColumnIndex) {
        return delegateTable.getStringAt(aRowIndex, aColumnIndex);
    }
}

On the framework level, SwingX should support WYSIWYE (What-you-see-is-what-you-export) out off the box, just as its other WYSIWYX: X = M for match, X = S for sort, X = F for filter. Raised issue 1477 in the SwingX issue tracker

kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • in an application at work we already have this behavior for a `JXTable` based on the `SwingX StringValue`interface. We have implemented this interface not only for rendering but also for the `TransferHandler`. It required some custom code which would be nice to have out-of-the-box but if interested I can see whether I can get an SSCCE of that code – Robin Jan 08 '12 at 19:28
  • @Robin cool! You bet that I'm interested (and win :-). Curious if/how the StringValueProvider (in JXTable and other JX* collection components) is/might be helpful in that task, too. – kleopatra Jan 09 '12 at 09:20
  • I updated my answer including a SwingX based example. Code lacks comments but I added some clarification/remarks at the start of my edit. Probably not the nicest solution, but it avoided code duplication and seems to work just fine – Robin Jan 10 '12 at 15:04
1

Have you tried to disable cell editing on the password column?

You can do this by extending the JTable class and replacing the isCellEditable function with your own implementation.

  • no, you don't override the isCellEditable on the JTable, you do so on the _TableModel_ Plus it doesn't sound like a problem of editing (hard to tell though, given the sparse info in the question), it's the default object --> string mapping in the default copy action, as @Robin already noted – kleopatra Jan 08 '12 at 13:10
1

Using the example shown here, I am able to paste, but not copy or cut, the password. This happens regardless of the visibility of the echoChar setting. Do you see a different result?

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045