001package jmri.jmrit.permission;
002
003import java.awt.GraphicsEnvironment;
004import java.io.*;
005import java.util.*;
006
007import jmri.*;
008import jmri.jmrit.XmlFile;
009import jmri.util.FileUtil;
010import jmri.util.ThreadingUtil;
011import jmri.util.swing.JmriJOptionPane;
012
013import org.jdom2.*;
014
015/**
016 * Default permission manager.
017 *
018 * @author Daniel Bergqvist (C) 2024
019 */
020public class DefaultPermissionManager implements PermissionManager {
021
022    private static final DefaultUser USER_GUEST =
023            new DefaultUser(Bundle.getMessage("PermissionManager_User_Guest").toLowerCase(),
024                    null, 50, "GUEST", new Role[]{DefaultRole.ROLE_GUEST});
025
026    private static final DefaultUser REMOTE_USER_GUEST =
027            new DefaultUser(Bundle.getMessage("PermissionManager_Remote_User_Guest"),
028                    null, 50, "REMOTE_GUEST", new Role[]{DefaultRole.ROLE_REMOTE_GUEST});
029
030    private static final DefaultUser USER_ADMIN =
031            new DefaultUser(Bundle.getMessage("PermissionManager_User_Admin").toLowerCase(),
032                    "jmri", 100, "ADMIN", new Role[]{DefaultRole.ROLE_ADMIN, DefaultRole.ROLE_STANDARD_USER});
033
034    private final Map<String, DefaultRole> _roles = new HashMap<>();
035    private final Map<String, DefaultUser> _users = new HashMap<>();
036    private final Set<PermissionOwner> _owners = new HashSet<>();
037    private final Set<Permission> _permissions = new HashSet<>();
038    private final Map<String, Permission> _permissionClassNames = new HashMap<>();
039    private final List<LoginListener> _loginListeners = new ArrayList<>();
040    private final Map<String, DefaultUser> _remoteUsers = new HashMap<>();
041
042    private boolean _permissionsEnabled = false;
043    private boolean _allowEmptyPasswords = false;
044    private User _currentUser = USER_GUEST;
045
046
047    DefaultPermissionManager() {
048        // Do nothing. The class is initialized by the method init().
049    }
050
051    DefaultPermissionManager(DefaultPermissionManager source) {
052        _permissionsEnabled = source._permissionsEnabled;
053        _allowEmptyPasswords = source._allowEmptyPasswords;
054        _owners.addAll(source._owners);
055        _permissions.addAll(source._permissions);
056        _permissionClassNames.putAll(source._permissionClassNames);
057        for (var entry : source._roles.entrySet()) {
058            _roles.put(entry.getKey(), new DefaultRole(entry.getValue()));
059        }
060        for (var entry : source._users.entrySet()) {
061            _users.put(entry.getKey(), new DefaultUser(entry.getValue()));
062        }
063    }
064
065    /**
066     * Return a copy of this PermissionManager.
067     * @return the copy
068     */
069    public DefaultPermissionManager getTemporaryInstance() {
070        return new DefaultPermissionManager(this);
071    }
072
073    synchronized DefaultPermissionManager init() {
074        _roles.put(DefaultRole.ROLE_GUEST.getName(), DefaultRole.ROLE_GUEST);
075        _roles.put(DefaultRole.ROLE_REMOTE_GUEST.getName(), DefaultRole.ROLE_REMOTE_GUEST);
076        _roles.put(DefaultRole.ROLE_STANDARD_USER.getName(), DefaultRole.ROLE_STANDARD_USER);
077        _roles.put(DefaultRole.ROLE_ADMIN.getName(), DefaultRole.ROLE_ADMIN);
078
079        _users.put(USER_GUEST.getUserName(), USER_GUEST);
080        _users.put(REMOTE_USER_GUEST.getUserName(), REMOTE_USER_GUEST);
081        _users.put(USER_ADMIN.getUserName(), USER_ADMIN);
082
083        for (PermissionFactory factory : ServiceLoader.load(PermissionFactory.class)) {
084            factory.register(this);
085        }
086        loadPermissionSettings();
087        ThreadingUtil.runOnGUIEventually(() -> {
088            checkThatAllRolesKnowsAllPermissions();
089        });
090        return this;
091    }
092
093    public synchronized Collection<Role> getRoles() {
094        return Collections.unmodifiableSet(new HashSet<>(_roles.values()));
095    }
096
097    public synchronized Collection<DefaultUser> getUsers() {
098        return Collections.unmodifiableSet(new HashSet<>(_users.values()));
099    }
100
101    public synchronized Set<PermissionOwner> getOwners() {
102        return Collections.unmodifiableSet(new HashSet<>(_owners));
103    }
104
105    public synchronized Set<Permission> getPermissions(PermissionOwner owner) {
106        Set<Permission> set = new HashSet<>();
107        for (Permission p : _permissions) {
108            if (p.getOwner().equals(owner)) {
109                set.add(p);
110            }
111        }
112        return Collections.unmodifiableSet(set);
113    }
114
115    private DefaultRole getSystemRole(String systemName) {
116        for (DefaultRole role : _roles.values()) {
117            if (role.isSystemRole() && role.getSystemName().equals(systemName)) {
118                return role;
119            }
120        }
121        return null;
122    }
123
124    private DefaultUser getSystemUser(String systemUsername) {
125        for (User u : _users.values()) {
126            DefaultUser du = (DefaultUser)u;
127            if (du.isSystemUser() && du.getSystemUsername().equals(systemUsername)) {
128                return du;
129            }
130        }
131        return null;
132    }
133
134    private void loadPermissionSettings() {
135        File file = new File(FileUtil.getPreferencesPath() + ".permissions.xml");
136
137        log.info("Permission file: {}", file.getAbsolutePath());
138
139        if (file.exists() && file.length() != 0) {
140            try {
141                Element root = new XmlFile().rootFromFile(file);
142
143                Element settings = root.getChild("Settings");
144                _permissionsEnabled = "yes".equals(settings.getChild("Enabled").getValue());
145                _allowEmptyPasswords = "yes".equals(settings.getChild("AllowEmptyPasswords").getValue());
146                log.info("Permission system is enabled: {}", _permissionsEnabled ? "yes" : "no");
147
148                List<Element> roleElementList = root.getChild("Roles").getChildren("Role");
149                for (Element roleElement : roleElementList) {
150                    Element systemNameElement = roleElement.getChild("SystemName");
151                    DefaultRole role;
152                    if (systemNameElement != null) {
153                        role = getSystemRole(systemNameElement.getValue());
154                        if (role == null) {
155                            log.error("SystemRole {} is not found.", systemNameElement.getValue());
156                            continue;
157                        }
158                    } else {
159                        role = new DefaultRole(roleElement.getChild("Name").getValue());
160                        _roles.put(role.getName(), role);
161                    }
162
163                    List<Element> permissionElementList = roleElement
164                            .getChild("Permissions").getChildren("Permission");
165                    for (Element permissionElement : permissionElementList) {
166                        String className = permissionElement.getChild("Class").getValue();
167                        Permission permission = _permissionClassNames.get(className);
168                        if (permission != null) {
169                            PermissionValue value = permission.valueOf(permissionElement.getChild("Enabled").getValue());
170                            role.setPermissionWithoutCheck(permission, value);
171                        } else {
172                            log.error("Permission class {} does not exists", className);
173                        }
174                    }
175                }
176
177                List<Element> userElementList = root.getChild("Users").getChildren("User");
178                for (Element userElement : userElementList) {
179
180                    Element systemNameElement = userElement.getChild("SystemUsername");
181                    DefaultUser user;
182                    if (systemNameElement != null) {
183                        user = getSystemUser(systemNameElement.getValue());
184                        if (user == null) {
185                            log.error("SystemUser {} is not found.", systemNameElement.getValue());
186                            continue;
187                        }
188                        Element passwordElement = userElement.getChild("Password");
189                        if (passwordElement != null) {
190                            user.setPasswordMD5(passwordElement.getValue());
191                            user.setSeed(userElement.getChild("Seed").getValue());
192                        }
193                    } else {
194                        user = new DefaultUser(
195                                userElement.getChild("Username").getValue(),
196                                userElement.getChild("Password").getValue(),
197                                userElement.getChild("Seed").getValue());
198                        _users.put(user.getUserName(), user);
199                    }
200
201                    user.setName(userElement.getChild("Name").getValue());
202                    user.setComment(userElement.getChild("Comment").getValue());
203
204                    Set<Role> roles = new HashSet<>();
205
206                    List<Element> userRoleElementList = userElement.getChild("Roles").getChildren("Role");
207                    for (Element roleElement : userRoleElementList) {
208                        Element roleSystemNameElement = roleElement.getChild("SystemName");
209                        Role role;
210                        if (roleSystemNameElement != null) {
211                            role = getSystemRole(roleSystemNameElement.getValue());
212                            if (role == null) {
213                                log.error("SystemRole {} is not found.", roleSystemNameElement.getValue());
214                                continue;
215                            }
216                        } else {
217                            role = _roles.get(roleElement.getChild("Name").getValue());
218                            if (role == null) {
219                                log.error("UserRole {} is not found.", roleElement.getValue());
220                                continue;
221                            }
222                        }
223                        roles.add(role);
224                    }
225                    user.setRoles(roles);
226                }
227
228            } catch (JDOMException | IOException ex) {
229                log.error("Exception during loading of permissions", ex);
230            }
231        } else {
232            log.info("Permission file not found or empty");
233        }
234    }
235
236    @Override
237    public synchronized void storePermissionSettings() {
238        File file = new File(FileUtil.getPreferencesPath() + ".permissions.xml");
239
240        try {
241            // Document doc = newDocument(root, dtdLocation+"layout-config-"+dtdVersion+".dtd");
242            Element rootElement = new Element("Permissions");
243
244            Element settings = new Element("Settings");
245            settings.addContent(new Element("Enabled")
246                    .addContent(this._permissionsEnabled ? "yes" : "no"));
247            settings.addContent(new Element("AllowEmptyPasswords")
248                    .addContent(this._allowEmptyPasswords ? "yes" : "no"));
249            rootElement.addContent(settings);
250
251            checkThatAllRolesKnowsAllPermissions();
252
253            Element rolesElement = new Element("Roles");
254            for (Role role : _roles.values()) {
255                Element roleElement = new Element("Role");
256                if (role.isSystemRole()) {
257                    roleElement.addContent(new Element("SystemName").addContent(role.getSystemName()));
258                }
259                roleElement.addContent(new Element("Name").addContent(role.getName()));
260
261                Element rolePermissions = new Element("Permissions");
262                for (java.util.Map.Entry<jmri.Permission, jmri.PermissionValue> entry : role.getPermissions().entrySet()) {
263                    Permission permission = entry.getKey();
264                    PermissionValue permissionValue = entry.getValue();
265                    Element userPermission = new Element("Permission");
266                    userPermission.addContent(new Element("Class").addContent(entry.getKey().getClass().getName()));
267                    userPermission.addContent(new Element("Enabled").addContent(permission.getValue(permissionValue)));
268                    rolePermissions.addContent(userPermission);
269                }
270                roleElement.addContent(rolePermissions);
271                rolesElement.addContent(roleElement);
272            }
273            rootElement.addContent(rolesElement);
274
275
276            Element usersElement = new Element("Users");
277            for (DefaultUser user : _users.values()) {
278                Element userElement = new Element("User");
279                if (user.isSystemUser()) {
280                    userElement.addContent(new Element("SystemUsername").addContent(user.getSystemUsername()));
281                }
282                userElement.addContent(new Element("Username").addContent(user.getUserName()));
283
284                if (user.getPassword() != null) {   // Guest user password is null
285                    userElement.addContent(new Element("Password").addContent(user.getPassword()));
286                    userElement.addContent(new Element("Seed").addContent(user.getSeed()));
287                }
288
289                userElement.addContent(new Element("Name").addContent(user.getName()));
290                userElement.addContent(new Element("Comment").addContent(user.getComment()));
291
292                Element userRolesElement = new Element("Roles");
293                for (Role role : user.getRoles()) {
294                    Element roleElement = new Element("Role");
295                    if (role.isSystemRole()) {
296                        roleElement.addContent(new Element("SystemName")
297                                .addContent(role.getSystemName()));
298                    }
299                    roleElement.addContent(new Element("Name").addContent(role.getName()));
300                    userRolesElement.addContent(roleElement);
301                }
302                userElement.addContent(userRolesElement);
303                usersElement.addContent(userElement);
304            }
305            rootElement.addContent(usersElement);
306
307            Document doc = XmlFile.newDocument(rootElement);
308            new XmlFile().writeXML(file, doc);
309
310        } catch (java.io.FileNotFoundException ex3) {
311            log.error("FileNotFound error writing file: {}", file);
312        } catch (java.io.IOException ex2) {
313            log.error("IO error writing file: {}", file);
314        }
315    }
316
317    @Override
318    public synchronized Role addRole(String name) throws RoleAlreadyExistsException {
319        if (_users.containsKey(name)) {
320            throw new RoleAlreadyExistsException();
321        }
322        DefaultRole role = new DefaultRole(name);
323        _roles.put(name, role);
324        return role;
325    }
326
327    @Override
328    public synchronized void removeRole(String name) throws RoleDoesNotExistException {
329
330        if (!_roles.containsKey(name)) {
331            throw new RoleDoesNotExistException();
332        }
333        _roles.remove(name);
334    }
335
336    @Override
337    public synchronized User addUser(String username, String password)
338            throws UserAlreadyExistsException {
339
340        String u = username.toLowerCase();
341        if (_users.containsKey(u)) {
342            throw new UserAlreadyExistsException();
343        }
344        DefaultUser user = new DefaultUser(u, password);
345        _users.put(u, user);
346        return user;
347    }
348
349    @Override
350    public synchronized void removeUser(String username)
351            throws UserDoesNotExistException {
352
353        if (!_users.containsKey(username)) {
354            throw new UserDoesNotExistException();
355        }
356        _users.remove(username);
357    }
358
359    @Override
360    public synchronized void changePassword(String newPassword, String oldPassword) {
361        if (_currentUser.changePassword(newPassword,  oldPassword)) {
362            storePermissionSettings();
363        }
364    }
365
366    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST",
367        justification="The text is from an exception")
368    @Override
369    public boolean login(String username, String password) {
370
371        synchronized(this) {
372            DefaultUser newUser = _users.get(username);
373
374            if (newUser == null || !newUser.checkPassword(password)) {
375                String msg = new BadUserOrPasswordException().getMessage();
376
377                if (!GraphicsEnvironment.isHeadless()) {
378                    JmriJOptionPane.showMessageDialog(null,
379                            msg,
380                            jmri.Application.getApplicationName(),
381                            JmriJOptionPane.ERROR_MESSAGE);
382                } else {
383                    log.error(msg);
384                }
385                return false;
386            }
387
388            _currentUser = newUser;
389        }
390        notifyLoginListeners(true);
391        return true;
392    }
393
394    @Override
395    public boolean remoteLogin(StringBuilder sessionId, Locale locale,
396            String username, String password) {
397
398        synchronized(this) {
399            DefaultUser newUser = _users.get(username);
400            if (newUser == null || !newUser.checkPassword(password)) {
401                return false;
402            }
403            if (sessionId.length() == 0) {
404                sessionId.append(DefaultUser.getRandomString(10));
405            }
406            _remoteUsers.put(sessionId.toString(), newUser);
407        }
408        // notifyLoginListeners(true);
409        return true;
410    }
411
412    @Override
413    public void logout() {
414        synchronized(this) {
415            _currentUser = USER_GUEST;
416        }
417        notifyLoginListeners(false);
418    }
419
420    @Override
421    public synchronized void remoteLogout(String sessionId) {
422        if (sessionId == null || sessionId.isBlank() || !_remoteUsers.containsKey(sessionId)) {
423            return;
424        }
425        _remoteUsers.remove(sessionId);
426    }
427
428    private void notifyLoginListeners(boolean isLogin) {
429        for (LoginListener listener : _loginListeners) {
430            listener.loginLogout(isLogin);
431        }
432    }
433
434   @Override
435    public synchronized boolean isLoggedIn() {
436        return _currentUser != USER_GUEST;
437    }
438
439    @Override
440    public synchronized boolean isRemotelyLoggedIn(String sessionId) {
441        return sessionId != null
442                && !sessionId.isBlank()
443                && _remoteUsers.containsKey(sessionId);
444    }
445
446    @Override
447    public synchronized boolean isCurrentUser(String username) {
448        return _currentUser.getUserName().equals(username);
449    }
450
451    @Override
452    public synchronized boolean isCurrentUser(User user) {
453        return _currentUser == user;
454    }
455
456    @Override
457    public synchronized String getCurrentUserName() {
458        return _currentUser != null ? _currentUser.getUserName() : null;
459    }
460
461    @Override
462    public synchronized boolean isAGuestUser(String username) {
463        DefaultUser user = _users.get(username);
464        if (user != null) {
465            String systemUsername = user.getSystemUsername();
466            return USER_GUEST.getSystemUsername().equals(systemUsername)
467                    || REMOTE_USER_GUEST.getSystemUsername().equals(systemUsername);
468        }
469        return false;
470    }
471
472    @Override
473    public synchronized boolean isAGuestUser(User user) {
474        if (user instanceof DefaultUser) {
475            String systemUsername = ((DefaultUser)user).getSystemUsername();
476            return USER_GUEST.getSystemUsername().equals(systemUsername)
477                    || REMOTE_USER_GUEST.getSystemUsername().equals(systemUsername);
478        }
479        return false;
480    }
481
482    @Override
483    public synchronized boolean isCurrentUserPermittedToChangePassword() {
484        return _currentUser != null && _currentUser.isPermittedToChangePassword();
485    }
486
487    @Override
488    public synchronized void addLoginListener(LoginListener listener) {
489        _loginListeners.add(listener);
490    }
491
492    @Override
493    public synchronized boolean isEnabled() {
494        return _permissionsEnabled;
495    }
496
497    @Override
498    public synchronized void setEnabled(boolean enabled) {
499        if (! InstanceManager.getDefault(PermissionManager.class)
500                .ensureAtLeastPermission(PermissionsSystemAdmin.PERMISSION_EDIT_PREFERENCES,
501                        BooleanPermission.BooleanValue.TRUE)) {
502            return;
503        }
504        _permissionsEnabled = enabled;
505    }
506
507    @Override
508    public synchronized boolean isAllowEmptyPasswords() {
509        return _allowEmptyPasswords;
510    }
511
512    @Override
513    public synchronized void setAllowEmptyPasswords(boolean value) {
514        if (! InstanceManager.getDefault(PermissionManager.class)
515                .ensureAtLeastPermission(PermissionsSystemAdmin.PERMISSION_EDIT_PREFERENCES,
516                        BooleanPermission.BooleanValue.TRUE)) {
517            return;
518        }
519        _allowEmptyPasswords = value;
520    }
521
522    @Override
523    public synchronized boolean hasAtLeastPermission(
524            Permission permission, PermissionValue minValue) {
525        return !_permissionsEnabled || _currentUser.hasAtLeastPermission(permission, minValue);
526    }
527
528    @Override
529    public synchronized boolean hasAtLeastRemotePermission(
530            String sessionId, Permission permission, PermissionValue minValue) {
531
532        if (!_permissionsEnabled) return true;
533
534        DefaultUser user = REMOTE_USER_GUEST;
535        if (sessionId != null && !sessionId.isBlank() && _remoteUsers.containsKey(sessionId)) {
536            user = _remoteUsers.get(sessionId);
537        }
538//        log.error("hasPermission: sessionId: {}, user: {}, permission: {}, has: {}", sessionId, user.getUserName(), permission.getName(), user.hasAtLeastPermission(permission, minValue));
539        return user.hasAtLeastPermission(permission, minValue);
540    }
541
542    @Override
543    public synchronized boolean ensureAtLeastPermission(
544            Permission permission, PermissionValue minValue) {
545        return !_permissionsEnabled || _currentUser.ensureAtLeastPermission(permission, minValue);
546    }
547
548    @Override
549    public synchronized void registerOwner(PermissionOwner owner) {
550        _owners.add(owner);
551    }
552
553    @Override
554    public synchronized void registerPermission(Permission permission) {
555        if (!_owners.contains(permission.getOwner())) {
556            throw new RuntimeException(String.format(
557                    "Permission class %s has an owner that's not known: %s",
558                    permission.getClass().getName(), permission.getOwner()));
559        }
560        _permissions.add(permission);
561        _permissionClassNames.put(permission.getClass().getName(), permission);
562    }
563
564    private void checkThatAllRolesKnowsAllPermissions() {
565        for (Role role : _roles.values()) {
566            for (Permission p : _permissions) {
567                if (!role.getPermissions().containsKey(p)) {
568                    ((DefaultRole)role).setPermissionWithoutCheck(
569                            p, p.getDefaultPermission(role));
570                }
571            }
572        }
573    }
574
575    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultPermissionManager.class);
576}