001package jmri.jmrit.permission.swing;
002
003import java.awt.*;
004import java.util.*;
005import java.util.List;
006import java.util.function.BooleanSupplier;
007
008import javax.swing.*;
009import javax.swing.border.EmptyBorder;
010
011import jmri.*;
012import jmri.PermissionsSystemAdmin;
013import jmri.jmrit.permission.DefaultPermissionManager;
014import jmri.swing.*;
015import jmri.util.swing.JmriJOptionPane;
016
017import org.openide.util.lookup.ServiceProvider;
018
019/**
020 * Preferences panel for Permission manager.
021 *
022 * @author Daniel Bergqvist Copyright 2024
023 */
024@ServiceProvider(service = PreferencesPanel.class)
025public class PermissionPreferencesPanel extends JPanel implements PreferencesPanel {
026
027    private final DefaultPermissionManager _temporaryPermissionManager;
028    private final Map<User, UserFields> _userFieldsMap = new HashMap<>();
029    private boolean _dirty = false;
030
031    public PermissionPreferencesPanel() {
032        PermissionManager mngr = InstanceManager.getDefault(PermissionManager.class);
033        if (!(mngr instanceof DefaultPermissionManager)) {
034            throw new RuntimeException("PermissionManager is not of type DefaultPermissionManager");
035        }
036        _temporaryPermissionManager = ((DefaultPermissionManager)mngr).getTemporaryInstance();
037        initGUI();
038    }
039
040    private void initGUI() {
041
042        JTabbedPane rolesTabbedPane = new JTabbedPane();
043        JTabbedPane usersTabbedPane = new JTabbedPane();
044
045        List<Role> roleList = new ArrayList<>(_temporaryPermissionManager.getRoles());
046        roleList.sort((a,b) -> {
047            if (a.getPriority() != b.getPriority()) {
048                return Integer.compare(b.getPriority(), a.getPriority());
049            }
050            return a.getName().toLowerCase().compareTo(b.getName().toLowerCase());
051        });
052
053        List<User> userList = new ArrayList<>(_temporaryPermissionManager.getUsers());
054        userList.sort((a,b) -> {
055            if (a.getPriority() != b.getPriority()) {
056                return Integer.compare(b.getPriority(), a.getPriority());
057            }
058            return a.getUserName().toLowerCase().compareTo(b.getUserName().toLowerCase());
059        });
060
061
062        JPanel outerPanel = new JPanel();
063
064        outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.PAGE_AXIS));
065
066        JPanel settingsPanel = new JPanel();
067        settingsPanel.setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS));
068        settingsPanel.setBorder(BorderFactory.createCompoundBorder(
069                BorderFactory.createLineBorder(Color.black, 1), new EmptyBorder(4,4,4,4)));
070
071        JCheckBox enablePermissionManagerCheckBox = new JCheckBox(Bundle.getMessage(
072                "PermissionPreferencesPanel_EnablePermissionManager"));
073        enablePermissionManagerCheckBox.setSelected(_temporaryPermissionManager.isEnabled());
074        enablePermissionManagerCheckBox.addActionListener((evt) -> {
075            if (enablePermissionManagerCheckBox.isSelected()) {
076                // Ask for confirmation before turning Permission Manager on
077                if (JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("PermissionPreferencesPanel_WarnStartPermissions"), Bundle.getMessage("WarningTitle"), JmriJOptionPane.YES_NO_OPTION)) {
078                    _temporaryPermissionManager.setEnabled(enablePermissionManagerCheckBox.isSelected());
079                    _dirty = true;
080                } else {
081                    enablePermissionManagerCheckBox.setSelected(false);
082                }
083            } else {
084                _temporaryPermissionManager.setEnabled(false);
085                _dirty = true;
086            }
087        });
088        settingsPanel.add(enablePermissionManagerCheckBox);
089
090        JCheckBox allowEmptyPasswordsCheckBox = new JCheckBox(Bundle.getMessage(
091                "PermissionPreferencesPanel_AllowEmptyPasswords"));
092        allowEmptyPasswordsCheckBox.setSelected(_temporaryPermissionManager.isAllowEmptyPasswords());
093        allowEmptyPasswordsCheckBox.addActionListener((evt) -> {
094            _temporaryPermissionManager.setAllowEmptyPasswords(allowEmptyPasswordsCheckBox.isSelected());
095            _dirty = true;
096        });
097        settingsPanel.add(allowEmptyPasswordsCheckBox);
098
099        outerPanel.add(settingsPanel);
100
101        outerPanel.add(Box.createVerticalStrut(10));
102
103        JPanel rolesPanel = new JPanel();
104        rolesPanel.setLayout(new BoxLayout(rolesPanel, BoxLayout.PAGE_AXIS));
105
106        for (Role role : roleList) {
107            rolesTabbedPane.addTab(role.getName(), new JScrollPane(
108                    getRolePanel(role, rolesTabbedPane, usersTabbedPane,
109                            roleList, userList)));
110        }
111
112        rolesPanel.add(rolesTabbedPane);
113
114        JButton addRoleButton = new JButton(Bundle.getMessage("PermissionPreferencesPanel_AddRole"));
115        addRoleButton.addActionListener((evt) -> { createNewRole(rolesTabbedPane, usersTabbedPane, roleList, userList); });
116        rolesPanel.add(addRoleButton);
117
118
119        JPanel usersPanel = new JPanel();
120        usersPanel.setLayout(new BoxLayout(usersPanel, BoxLayout.PAGE_AXIS));
121
122        reloadUsersTabbedPane(usersTabbedPane, roleList, userList);
123
124        usersPanel.add(usersTabbedPane);
125
126        JButton addUserButton = new JButton(Bundle.getMessage("PermissionPreferencesPanel_AddUser"));
127        addUserButton.addActionListener((evt) -> {
128            new AddUserDialog(_temporaryPermissionManager, getFrame(), (user) -> {
129                // Find the index of the new user
130                userList.clear();
131                userList.addAll(_temporaryPermissionManager.getUsers());
132                userList.sort((a,b) -> {
133                    if (a.getPriority() != b.getPriority()) {
134                        return Integer.compare(b.getPriority(), a.getPriority());
135                    }
136                    return a.getUserName().toLowerCase().compareTo(b.getUserName().toLowerCase());
137                });
138                usersTabbedPane.insertTab(user.getUserName(), null,
139                        new JScrollPane(getUserPanel(user, usersTabbedPane, roleList, userList)),
140                        null, userList.indexOf(user));
141                getFrame().pack();
142                _dirty = true;
143            }).setVisible(true);
144        });
145        usersPanel.add(addUserButton);
146
147        usersPanel.add(Box.createGlue());
148
149        JTabbedPane tabbedPane = new JTabbedPane();
150        tabbedPane.addTab(Bundle.getMessage("PermissionPreferencesPanel_Roles"),
151                new JScrollPane(rolesPanel));
152        tabbedPane.addTab(Bundle.getMessage("PermissionPreferencesPanel_Users"),
153                new JScrollPane(usersPanel));
154
155        JPanel outerTabbedPanel = new JPanel();
156        outerTabbedPanel.add(tabbedPane);
157        outerPanel.add(outerTabbedPanel);
158        add(outerPanel);
159    }
160
161    private Frame getFrame() {
162        Container c = this;
163        while (c != null && !(c instanceof Frame)) {
164            c = c.getParent();
165        }
166        // c is either a Frame or null
167        return (Frame)c;
168    }
169
170    private void createNewRole(JTabbedPane rolesTabbedPane,
171            JTabbedPane usersTabbedPane, List<Role> roleList, List<User> userList) {
172
173        String roleName = JOptionPane.showInputDialog(getFrame(),
174                Bundle.getMessage("PermissionPreferencesPanel_EnterRoleName"));
175
176        if (roleName == null) {
177            return;     // User selected "Cancel"
178        }
179
180        if (roleName.isBlank()) {
181            JmriJOptionPane.showMessageDialog(null,
182                    Bundle.getMessage("PermissionPreferencesPanel_NameEmpty"),
183                    jmri.Application.getApplicationName(),
184                    JmriJOptionPane.ERROR_MESSAGE);
185            return;
186        }
187
188        if (!roleName.equals(roleName.trim())) {
189            JmriJOptionPane.showMessageDialog(null,
190                    Bundle.getMessage("PermissionPreferencesPanel_SpaceNotAllowedInRoleName"),
191                    jmri.Application.getApplicationName(),
192                    JmriJOptionPane.ERROR_MESSAGE);
193            return;
194        }
195
196        try {
197            Role role = _temporaryPermissionManager.addRole(roleName);
198
199            // Find the index of the new role
200            roleList.clear();
201            roleList.addAll(_temporaryPermissionManager.getRoles());
202            roleList.sort((a,b) -> {
203                if (a.getPriority() != b.getPriority()) {
204                    return Integer.compare(b.getPriority(), a.getPriority());
205                }
206                return a.getName().toLowerCase().compareTo(b.getName().toLowerCase());
207            });
208
209            rolesTabbedPane.insertTab(role.getName(), null,
210                    new JScrollPane(getRolePanel(role, rolesTabbedPane,
211                            usersTabbedPane, roleList, userList)),
212                    null, roleList.indexOf(role));
213
214            reloadUsersTabbedPane(usersTabbedPane, roleList, userList);
215            getFrame().pack();
216            _dirty = true;
217
218        } catch (PermissionManager.RoleAlreadyExistsException e) {
219            JmriJOptionPane.showMessageDialog(null,
220                    Bundle.getMessage("PermissionPreferencesPanel_RoleNameExists"),
221                    jmri.Application.getApplicationName(),
222                    JmriJOptionPane.ERROR_MESSAGE);
223        }
224    }
225
226    private JPanel getRolePanel(Role role, JTabbedPane rolesTabbedPane,
227            JTabbedPane usersTabbedPane, List<Role> roleList, List<User> userList) {
228
229        JPanel rolePanel = new JPanel();
230        rolePanel.setLayout(new GridBagLayout());
231        GridBagConstraints c = new GridBagConstraints();
232        c.gridwidth = 3;
233        c.gridheight = 1;
234        c.gridx = 0;
235        c.gridy = 0;
236        c.anchor = java.awt.GridBagConstraints.WEST;
237
238        JLabel roleLabel = new JLabel("<html><font size=\"+1\"><b>"+role.getName()+"</b></font></html>");
239        roleLabel.setBorder(new EmptyBorder(4,4,0,4));
240        rolePanel.add(roleLabel, c);
241        c.gridy++;
242
243        List<PermissionOwner> owners = new ArrayList<>(_temporaryPermissionManager.getOwners());
244        owners.sort((a,b) -> {return a.getName().compareTo(b.getName());});
245        for (PermissionOwner owner : owners) {
246
247            JLabel ownerLabel = new JLabel("<html><font size=\"0.5\"><b>"+owner.getName()+"</b></font></html>");
248            ownerLabel.setBorder(new EmptyBorder(15,4,4,4));
249            rolePanel.add(ownerLabel, c);
250            c.gridy++;
251
252            List<Permission> permissions = new ArrayList<>(_temporaryPermissionManager.getPermissions(owner));
253            permissions.sort((a,b) -> { return a.getName().compareTo(b.getName()); });
254            for (Permission permission : permissions) {
255                PermissionSwing permissionSwing =
256                        PermissionSwingTools.getPermissionSwingForClass(permission);
257                JLabel label = permissionSwing.getLabel(permission);
258                if (label != null) {
259                    c.gridwidth = 1;
260                    c.gridx = 0;
261                    rolePanel.add(label, c);
262                    c.gridx = 1;
263                    rolePanel.add(Box.createHorizontalStrut(5), c);
264                    c.gridx = 2;
265                }
266                rolePanel.add(permissionSwing.getComponent(
267                        role, permission, this::setDirtyFlag), c);
268                c.gridy++;
269                c.gridx = 0;
270                c.gridwidth = 3;
271            }
272        }
273
274        rolePanel.add(Box.createVerticalStrut(10));
275        JButton removeRoleButton = new JButton(Bundle.getMessage("PermissionPreferencesPanel_RemoveRole"));
276        removeRoleButton.addActionListener((evt) -> {
277            if (JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog(
278                            null,
279                            Bundle.getMessage("PermissionPreferencesPanel_RemoveRoleConfirmation", role.getName()),
280                            Bundle.getMessage("PermissionPreferencesPanel_RemoveRoleTitle"),
281                            JmriJOptionPane.YES_NO_OPTION)) {
282                try {
283                    _temporaryPermissionManager.removeRole(role.getName());
284                    rolesTabbedPane.remove(roleList.indexOf(role));
285                    roleList.remove(role);
286                    reloadUsersTabbedPane(usersTabbedPane, roleList, userList);
287                    getFrame().pack();
288                    _dirty = true;
289                } catch (PermissionManager.RoleDoesNotExistException e) {
290                    log.error("Unexpected exception", e);
291                }
292            }
293        });
294        if (role.isSystemRole()) {
295            removeRoleButton.setEnabled(false);
296        }
297        c.gridy++;
298        rolePanel.add(Box.createVerticalStrut(5), c);
299        c.gridy++;
300        c.anchor = GridBagConstraints.CENTER;
301        rolePanel.add(removeRoleButton, c);
302
303        return rolePanel;
304    }
305
306    private void setDirtyFlag() {
307        _dirty = true;
308    }
309
310    private void reloadUsersTabbedPane(JTabbedPane usersTabbedPane,
311            List<Role> roleList, List<User> userList) {
312
313        usersTabbedPane.removeAll();
314        for (User user : userList) {
315            usersTabbedPane.addTab(user.getUserName(), new JScrollPane(
316                    getUserPanel(user, usersTabbedPane, roleList, userList)));
317        }
318    }
319
320    private JPanel getUserPanel(User user, JTabbedPane usersTabbedPane,
321            List<Role> roleList, List<User> userList) {
322        JPanel userPanel = new JPanel();
323        userPanel.setLayout(new BoxLayout(userPanel, BoxLayout.PAGE_AXIS));
324
325        UserFields userFields = new UserFields();
326        _userFieldsMap.put(user, userFields);
327
328        JLabel usernameLabel = new JLabel("<html><font size=\"+1\"><b>"+user.getUserName()+"</b></font></html>");
329        usernameLabel.setBorder(new EmptyBorder(4,4,4,4));
330        userPanel.add(usernameLabel);
331        userPanel.add(new JLabel(Bundle.getMessage("PermissionPreferencesPanel_Name")));
332        userFields._nameTextField = new JTextField(20);
333        userFields._nameTextField.setText(user.getName());
334        userPanel.add(userFields._nameTextField);
335        userPanel.add(new JLabel(Bundle.getMessage("PermissionPreferencesPanel_Comment")));
336        userFields._commentTextField = new JTextField(40);
337        userFields._commentTextField.setText(user.getComment());
338        userPanel.add(userFields._commentTextField);
339
340        userPanel.add(Box.createVerticalStrut(10));
341
342        userPanel.add(new JLabel(Bundle.getMessage("PermissionPreferencesPanel_Roles")));
343        userPanel.add(Box.createVerticalStrut(5));
344
345        int lastPriority = 0;
346        for (Role role : roleList) {
347            if (role.getPriority() == 0 && lastPriority != 0) {
348                userPanel.add(Box.createVerticalStrut(10));
349            }
350            JCheckBox checkBox = new JCheckBox(role.getName());
351            checkBox.setSelected(user.getRoles().contains(role));
352            checkBox.addActionListener((evt) -> {
353                if (checkBox.isSelected()) {
354                    user.addRole(role);
355                } else {
356                    user.removeRole(role);
357                }
358                _dirty = true;
359            });
360            userPanel.add(checkBox);
361            lastPriority = role.getPriority();
362        }
363
364        userPanel.add(Box.createVerticalStrut(10));
365
366        JButton changePasswordButton = new JButton(Bundle.getMessage("PermissionPreferencesPanel_ChangePassword"));
367        changePasswordButton.setEnabled(!_temporaryPermissionManager.isAGuestUser(user));
368        changePasswordButton.addActionListener((evt) -> {
369            new ChangeUserPasswordDialog(
370                    _temporaryPermissionManager, getFrame(), user, ()->{_dirty = true;})
371                    .setVisible(true);
372        });
373        userPanel.add(changePasswordButton);
374
375        JButton removeUserButton = new JButton(Bundle.getMessage("PermissionPreferencesPanel_RemoveUser"));
376        removeUserButton.addActionListener((evt) -> {
377            if (JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog(
378                            null,
379                            Bundle.getMessage("PermissionPreferencesPanel_RemoveUserConfirmation", user.getUserName(), user.getName()),
380                            Bundle.getMessage("PermissionPreferencesPanel_RemoveUserTitle"),
381                            JmriJOptionPane.YES_NO_OPTION)) {
382                try {
383                    _temporaryPermissionManager.removeUser(user.getUserName());
384                    usersTabbedPane.remove(userList.indexOf(user));
385                    userList.remove(user);
386                    _dirty = true;
387                } catch (PermissionManager.UserDoesNotExistException e) {
388                    log.error("Unexpected exception", e);
389                }
390            }
391        });
392        if (user.isSystemUser()) {
393            removeUserButton.setEnabled(false);
394        }
395        userPanel.add(removeUserButton);
396
397        return userPanel;
398    }
399
400    @Override
401    public String getPreferencesItem() {
402        return "PREFERENCES"; // NOI18N
403    }
404
405    @Override
406    public String getPreferencesItemText() {
407        return Bundle.getMessage("MenuPermission"); // NOI18N
408    }
409
410    @Override
411    public String getTabbedPreferencesTitle() {
412        return getPreferencesItemText();
413    }
414
415    @Override
416    public String getLabelKey() {
417        return null;
418    }
419
420    @Override
421    public JComponent getPreferencesComponent() {
422        return this;
423    }
424
425    @Override
426    public boolean isPersistant() {
427        return false;
428    }
429
430    @Override
431    public String getPreferencesTooltip() {
432        return null;
433    }
434
435    @Override
436    public void savePreferences() {
437        for (var entry : _userFieldsMap.entrySet()) {
438            entry.getKey().setName(entry.getValue()._nameTextField.getText());
439            entry.getKey().setComment(entry.getValue()._commentTextField.getText());
440        }
441        _temporaryPermissionManager.storePermissionSettings();
442        _dirty = false;
443    }
444
445    @Override
446    public boolean isDirty() {
447        return _dirty;
448    }
449
450    @Override
451    public boolean isRestartRequired() {
452        return true;
453    }
454
455    @Override
456    public boolean isPreferencesValid() {
457        return true;
458    }
459
460//    @Override
461//    public int getSortOrder() {
462//        return PreferencesPanel.super.getSortOrder();
463//    }
464
465    @Override
466    public BooleanSupplier getIsEnabled() {
467        return () -> {
468            return InstanceManager.getDefault(PermissionManager.class)
469                    .ensureAtLeastPermission(PermissionsSystemAdmin.PERMISSION_EDIT_PREFERENCES,
470                            BooleanPermission.BooleanValue.TRUE);
471        };
472    }
473
474
475    private static class UserFields {
476        JTextField _nameTextField;
477        JTextField _commentTextField;
478    }
479
480
481    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PermissionPreferencesPanel.class);
482}