001package jmri.jmrit.permission; 002 003import java.awt.GraphicsEnvironment; 004import java.security.MessageDigest; 005import java.security.NoSuchAlgorithmException; 006import java.util.*; 007 008import javax.xml.bind.DatatypeConverter; 009 010import jmri.*; 011import jmri.util.swing.JmriJOptionPane; 012 013/** 014 * The default implementation of User. 015 * 016 * @author Daniel Bergqvist (C) 2024 017 */ 018@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="DMI_RANDOM_USED_ONLY_ONCE", 019 justification = "False positive. The Random instance is kept by the iterator.") 020 021public class DefaultUser implements User { 022 023 private final String _username; 024 private final String _systemUserName; 025 private final boolean _systemUser; 026 private final int _priority; 027 private String _seed; 028 private String _passwordMD5; 029 private String _name = ""; 030 private String _comment = ""; 031 032 private final Set<Role> _roles = new TreeSet<>((a,b) -> {return a.getName().compareTo(b.getName());}); 033 034 public DefaultUser(String username, String password) { 035 this(username, password, 0, null, new Role[]{}); 036 DefaultUser.this.addRole(DefaultRole.ROLE_STANDARD_USER); 037 } 038 039 DefaultUser(String username, String password, int priority, String systemUserName, Role[] roles) { 040 this._username = username; 041 this._priority = priority; 042 this._systemUser = priority != 0; 043 this._systemUserName = systemUserName; 044 if (password != null) { 045 this._seed = getRandomString(10); 046 try { 047 this._passwordMD5 = getPasswordSHA256(password); 048 } catch (NoSuchAlgorithmException e) { 049 log.error("MD5 algoritm doesn't exists", e); 050 } 051 } else { 052 this._seed = null; 053 } 054 for (Role role : roles) { 055 _roles.add(role); 056 } 057 } 058 059 public DefaultUser(String username, String passwordMD5, String seed) { 060 this._username = username; 061 this._systemUserName = null; 062 this._systemUser = false; 063 this._priority = 0; 064 this._passwordMD5 = passwordMD5; 065 this._seed = seed; 066 } 067 068 private static final PrimitiveIterator.OfInt iterator = 069 new Random().ints('a', 'z'+10).iterator(); 070 071 private String getRandomString(int count) { 072 StringBuilder s = new StringBuilder(); 073 for (int i=0; i < count; i++) { 074 int r = iterator.nextInt(); 075 char c = (char) (r > 'z' ? r-'z'+'0' : r); 076 s.append(c); 077 } 078 return s.toString(); 079 } 080 081 @Override 082 public String getUserName() { 083 return this._username; 084 } 085 086 @Override 087 public boolean isSystemUser() { 088 return this._systemUser; 089 } 090 091 @Override 092 public int getPriority() { 093 return this._priority; 094 } 095 096 String getSystemUsername() { 097 return this._systemUserName; 098 } 099 100 String getPassword() { 101 return this._passwordMD5; 102 } 103 104 void setPasswordMD5(String passwordMD5) { 105 this._passwordMD5 = passwordMD5; 106 } 107 108 String getSeed() { 109 return this._seed; 110 } 111 112 void setSeed(String seed) { 113 this._seed = seed; 114 } 115 116 @Override 117 public String getName() { 118 return _name; 119 } 120 121 @Override 122 public void setName(String name) { 123 this._name = name; 124 } 125 126 @Override 127 public String getComment() { 128 return _comment; 129 } 130 131 @Override 132 public void setComment(String comment) { 133 this._comment = comment; 134 } 135 136 @Override 137 public Set<Role> getRoles() { 138 return Collections.unmodifiableSet(_roles); 139 } 140 141 @Override 142 public void addRole(Role role) { 143 if (! InstanceManager.getDefault(PermissionManager.class) 144 .checkPermission(PermissionsSystemAdmin.PERMISSION_EDIT_PREFERENCES)) { 145 return; 146 } 147 _roles.add(role); 148 } 149 150 @Override 151 public void removeRole(Role role) { 152 if (! InstanceManager.getDefault(PermissionManager.class) 153 .checkPermission(PermissionsSystemAdmin.PERMISSION_EDIT_PREFERENCES)) { 154 return; 155 } 156 _roles.remove(role); 157 } 158 159 void setRoles(Set<Role> roles) { 160 _roles.clear(); 161 _roles.addAll(roles); 162 } 163 164 private String getPasswordSHA256(String password) throws NoSuchAlgorithmException { 165 String passwd = this._seed + password; 166 MessageDigest md = MessageDigest.getInstance("SHA-256"); 167 md.update(passwd.getBytes()); 168 return DatatypeConverter 169 .printHexBinary(md.digest()).toUpperCase(); 170 } 171 172 @Override 173 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 174 justification="The text is from an exception") 175 public void setPassword(String newPassword) { 176 PermissionManager pMngr = InstanceManager.getDefault(PermissionManager.class); 177 178 if (!pMngr.hasPermission( 179 PermissionsSystemAdmin.PERMISSION_EDIT_PERMISSIONS)) { 180 log.warn("The current user has not permission to change password for user {}", getUserName()); 181 182 if (!GraphicsEnvironment.isHeadless()) { 183 JmriJOptionPane.showMessageDialog(null, 184 Bundle.getMessage("DefaultPermissionManager_PermissionDenied"), 185 jmri.Application.getApplicationName(), 186 JmriJOptionPane.ERROR_MESSAGE); 187 } 188 } 189 190 try { 191 this._passwordMD5 = getPasswordSHA256(newPassword); 192 } catch (NoSuchAlgorithmException e) { 193 String msg = "MD5 algoritm doesn't exists"; 194 log.error(msg); 195 throw new RuntimeException(msg); 196 } 197 } 198 199 @Override 200 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 201 justification="The text is from an exception") 202 public boolean changePassword(String oldPassword, String newPassword) { 203 PermissionManager pMngr = InstanceManager.getDefault(PermissionManager.class); 204 205 boolean isCurrentUser = pMngr.isCurrentUser(this); 206 boolean hasEditPasswordPermission = pMngr.hasPermission( 207 PermissionsSystemAdmin.PERMISSION_EDIT_OWN_PASSWORD); 208 boolean hasAdminPermission = pMngr.hasPermission( 209 PermissionsSystemAdmin.PERMISSION_EDIT_PERMISSIONS); 210 211 if (hasAdminPermission || (isCurrentUser && hasEditPasswordPermission)) { 212 if (!checkPassword(oldPassword)) { 213 String msg = new PermissionManager.BadPasswordException().getMessage(); 214 215 if (!GraphicsEnvironment.isHeadless()) { 216 JmriJOptionPane.showMessageDialog(null, 217 msg, 218 jmri.Application.getApplicationName(), 219 JmriJOptionPane.ERROR_MESSAGE); 220 } else { 221 log.error(msg); 222 } 223 } else { 224 try { 225 this._passwordMD5 = getPasswordSHA256(newPassword); 226 return true; 227 } catch (NoSuchAlgorithmException e) { 228 String msg = "MD5 algoritm doesn't exists"; 229 log.error(msg); 230 throw new RuntimeException(msg); 231 } 232 } 233 } else { 234 if (pMngr.isCurrentUser(this)) { 235 log.warn("User {} has not permission to change its own password", getUserName()); 236 } else { 237 log.warn("The current user has not permission to change password for user {}", getUserName()); 238 } 239 if (!GraphicsEnvironment.isHeadless()) { 240 JmriJOptionPane.showMessageDialog(null, 241 Bundle.getMessage("DefaultPermissionManager_PermissionDenied"), 242 jmri.Application.getApplicationName(), 243 JmriJOptionPane.ERROR_MESSAGE); 244 } 245 } 246 return false; 247 } 248 249 public boolean checkPassword(String password) { 250 try { 251 return _passwordMD5.equals(getPasswordSHA256(password)); 252 } catch (NoSuchAlgorithmException e) { 253 String msg = "MD5 algoritm doesn't exists"; 254 log.error(msg); 255 throw new RuntimeException(msg); 256 } 257 } 258 259 @Override 260 public boolean hasPermission(Permission permission) { 261 for (Role role : _roles) { 262 if (role.hasPermission(permission)) return true; 263 } 264 return false; 265 } 266 267 @Override 268 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 269 justification="The text is from a bundle") 270 public boolean checkPermission(Permission permission) { 271 if (!hasPermission(permission)) { 272 log.warn("User {} has not permission {}", this.getUserName(), permission.getName()); 273 if (!GraphicsEnvironment.isHeadless()) { 274 JmriJOptionPane.showMessageDialog(null, 275 Bundle.getMessage("DefaultPermissionManager_PermissionDenied"), 276 jmri.Application.getApplicationName(), 277 JmriJOptionPane.ERROR_MESSAGE); 278 } 279 return false; 280 } 281 return true; 282 } 283 284 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultUser.class); 285}