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