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