001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.console.core.security; 019 020 import java.io.File; 021 import java.io.FileOutputStream; 022 import java.io.InputStream; 023 import java.io.IOException; 024 import java.io.OutputStream; 025 import java.net.URL; 026 import java.net.URLConnection; 027 import java.net.UnknownServiceException; 028 import java.security.MessageDigest; 029 import java.security.NoSuchAlgorithmException; 030 import java.util.Arrays; 031 import java.util.Enumeration; 032 import java.util.HashSet; 033 import java.util.Hashtable; 034 import java.util.Properties; 035 import java.util.Set; 036 037 import org.apache.commons.logging.Log; 038 import org.apache.commons.logging.LogFactory; 039 import org.apache.geronimo.common.GeronimoSecurityException; 040 import org.apache.geronimo.gbean.GBeanInfo; 041 import org.apache.geronimo.gbean.GBeanInfoBuilder; 042 import org.apache.geronimo.gbean.GBeanLifecycle; 043 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory; 044 import org.apache.geronimo.security.jaas.LoginModuleGBean; 045 import org.apache.geronimo.security.jaas.LoginModuleSettings; 046 import org.apache.geronimo.system.serverinfo.ServerInfo; 047 import org.apache.geronimo.crypto.encoders.Base64; 048 import org.apache.geronimo.crypto.encoders.HexTranslator; 049 import org.apache.geronimo.crypto.SimpleEncryption; 050 import org.apache.geronimo.crypto.EncryptionManager; 051 052 /** 053 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 054 */ 055 public class PropertiesLoginModuleManager implements GBeanLifecycle { 056 private static Log log = LogFactory.getLog(PropertiesLoginModuleManager.class); 057 058 private ServerInfo serverInfo; 059 060 private LoginModuleSettings loginModule; 061 062 private Properties users = new Properties(); 063 064 private Properties groups = new Properties(); 065 066 private static final String usersKey = "usersURI"; 067 068 private static final String groupsKey = "groupsURI"; 069 070 private static final String digestKey = "digest"; 071 072 private final static String encodingKey = "encoding"; 073 074 public PropertiesLoginModuleManager(ServerInfo serverInfo, LoginModuleSettings loginModule) { 075 this.serverInfo = serverInfo; 076 this.loginModule = loginModule; 077 } 078 079 private void refreshUsers() throws GeronimoSecurityException { 080 users.clear(); 081 InputStream in = null; 082 try { 083 in = serverInfo.resolveServer(getUsersURI()).toURL().openStream(); 084 users.load(in); 085 } catch (Exception e) { 086 throw new GeronimoSecurityException(e); 087 } finally { 088 if (in != null) { 089 try { 090 in.close(); 091 } catch (IOException ignored) { 092 // ignored 093 } 094 } 095 } 096 } 097 098 private void refreshGroups() throws GeronimoSecurityException { 099 groups.clear(); 100 InputStream in = null; 101 try { 102 in = serverInfo.resolveServer(getGroupsURI()).toURL().openStream(); 103 groups.load(in); 104 } catch (Exception e) { 105 throw new GeronimoSecurityException(e); 106 } finally { 107 if (in != null) { 108 try { 109 in.close(); 110 } catch (IOException ignored) { 111 // ignored 112 } 113 } 114 } 115 } 116 117 private void clearAll() { 118 users.clear(); 119 groups.clear(); 120 } 121 122 public void refreshAll() throws GeronimoSecurityException { 123 refreshGroups(); 124 refreshUsers(); 125 } 126 127 public String[] getUsers() throws GeronimoSecurityException { 128 refreshUsers(); 129 return (String[]) users.keySet().toArray(new String[0]); 130 } 131 132 public String[] getGroups() throws GeronimoSecurityException { 133 refreshGroups(); 134 return (String[]) groups.keySet().toArray(new String[0]); 135 } 136 137 public void addUserPrincipal(Hashtable properties) 138 throws GeronimoSecurityException { 139 140 refreshUsers(); 141 String name = (String) properties.get("UserName"); 142 if (users.getProperty(name) != null) { 143 log.warn("addUserPrincipal() UserName="+name+" already exists."); 144 throw new GeronimoSecurityException("User principal="+name+" already exists."); 145 } 146 try { 147 String realPassword = (String) properties.get("Password"); 148 if (realPassword != null) { 149 String digest = getDigest(); 150 if(digest != null && !digest.equals("")) { 151 realPassword = digestPassword(realPassword, digest, getEncoding()); 152 } 153 realPassword = EncryptionManager.encrypt(realPassword); 154 } 155 users.setProperty(name, realPassword); 156 store(users, serverInfo.resolveServer(getUsersURI()).toURL()); 157 } catch (Exception e) { 158 throw new GeronimoSecurityException("Cannot add user principal: " 159 + e.getMessage(), e); 160 } 161 } 162 163 public void removeUserPrincipal(String userPrincipal) 164 throws GeronimoSecurityException { 165 refreshUsers(); 166 try { 167 users.remove(userPrincipal); 168 store(users, serverInfo.resolveServer(getUsersURI()).toURL()); 169 } catch (Exception e) { 170 throw new GeronimoSecurityException("Cannot remove user principal " 171 + userPrincipal + ": " + e.getMessage(), e); 172 } 173 } 174 175 public void updateUserPrincipal(Hashtable properties) 176 throws GeronimoSecurityException { 177 refreshUsers(); 178 String name = (String) properties.get("UserName"); 179 if (users.getProperty(name) == null) { 180 log.warn("updateUserPrincipal() UserName="+name+" does not exist."); 181 throw new GeronimoSecurityException("User principal="+name+" does not exist."); 182 } 183 try { 184 String realPassword = (String) properties.get("Password"); 185 if (realPassword != null) { 186 String digest = getDigest(); 187 if(digest != null && !digest.equals("")) { 188 realPassword = digestPassword(realPassword, digest, getEncoding()); 189 } 190 realPassword = EncryptionManager.encrypt(realPassword); 191 } 192 users.setProperty(name, realPassword); 193 store(users, serverInfo.resolveServer(getUsersURI()).toURL()); 194 } catch (Exception e) { 195 throw new GeronimoSecurityException("Cannot update user principal: " 196 + e.getMessage(), e); 197 } 198 } 199 200 public void addGroupPrincipal(Hashtable properties) 201 throws GeronimoSecurityException { 202 refreshGroups(); 203 String group = (String) properties.get("GroupName"); 204 if (groups.getProperty(group) != null) { 205 log.warn("addGroupPrincipal() GroupName="+group+" already exists."); 206 throw new GeronimoSecurityException("Group principal="+group+" already exists."); 207 } 208 try { 209 groups.setProperty(group, (String) properties.get("Members")); 210 store(groups, serverInfo.resolveServer(getGroupsURI()).toURL()); 211 } catch (Exception e) { 212 throw new GeronimoSecurityException("Cannot add group principal: " 213 + e.getMessage(), e); 214 } 215 } 216 217 public void removeGroupPrincipal(String groupPrincipal) 218 throws GeronimoSecurityException { 219 refreshGroups(); 220 try { 221 groups.remove(groupPrincipal); 222 store(groups, serverInfo.resolveServer(getGroupsURI()).toURL()); 223 } catch (Exception e) { 224 throw new GeronimoSecurityException( 225 "Cannot remove group principal: " + e.getMessage(), e); 226 } 227 } 228 229 public void updateGroupPrincipal(Hashtable properties) 230 throws GeronimoSecurityException { 231 //same as add group principal 232 refreshGroups(); 233 String group = (String) properties.get("GroupName"); 234 if (groups.getProperty(group) == null) { 235 log.warn("updateGroupPrincipal() GroupName="+group+" does not exist."); 236 throw new GeronimoSecurityException("Group principal="+group+" does not exist."); 237 } 238 try { 239 groups.setProperty(group, (String) properties.get("Members")); 240 store(groups, serverInfo.resolveServer(getGroupsURI()).toURL()); 241 } catch (Exception e) { 242 throw new GeronimoSecurityException("Cannot update group principal: " 243 + e.getMessage(), e); 244 } 245 } 246 247 public void addToGroup(String userPrincipal, String groupPrincipal) 248 throws GeronimoSecurityException { 249 throw new GeronimoSecurityException( 250 "Not implemented for properties file security realm..."); 251 } 252 253 public void removeFromGroup(String userPrincipal, String groupPrincipal) 254 throws GeronimoSecurityException { 255 throw new GeronimoSecurityException( 256 "Not implemented for properties file security realm..."); 257 } 258 259 public String getPassword(String userPrincipal) 260 throws GeronimoSecurityException { 261 refreshUsers(); 262 if (users.getProperty(userPrincipal) == null) { 263 log.warn("getPassword() User="+userPrincipal+" does not exist."); 264 throw new GeronimoSecurityException("User principal="+userPrincipal+" does not exist."); 265 } 266 String realPassword = users.getProperty(userPrincipal); 267 if (realPassword != null) { 268 realPassword = (String) EncryptionManager.decrypt(realPassword); 269 } 270 return realPassword; 271 } 272 273 public Set getGroupMembers(String groupPrincipal) 274 throws GeronimoSecurityException { 275 Set memberSet = new HashSet(); 276 // return nothing when the groupPrincipal is null or empty 277 if (groupPrincipal == null || groupPrincipal.equals("")) { 278 return memberSet; 279 } 280 refreshGroups(); 281 if (groups.getProperty(groupPrincipal) == null) { 282 log.warn("getGroupMembers() Group="+groupPrincipal+" does not exist."); 283 return memberSet; 284 } 285 String[] members = ((String)groups.getProperty(groupPrincipal)).split(","); 286 287 memberSet.addAll(Arrays.asList(members)); 288 return memberSet; 289 } 290 291 private String getUsersURI() { 292 return (String) loginModule.getOptions().get(usersKey); 293 } 294 295 private String getGroupsURI() { 296 return (String) loginModule.getOptions().get(groupsKey); 297 } 298 299 private String getDigest() { 300 return (String) loginModule.getOptions().get(digestKey); 301 } 302 303 private String getEncoding() { 304 return (String) loginModule.getOptions().get(encodingKey); 305 } 306 307 /** 308 * Allows the GBean at startup to request that all unencrypted passwords 309 * be updated. 310 */ 311 private void encryptAllPasswords() throws GeronimoSecurityException { 312 log.debug("Checking passwords to see if any need encrypting"); 313 refreshAll(); 314 try { 315 String name; 316 boolean bUpdates=false; 317 318 for (Enumeration e=users.keys(); e.hasMoreElements(); ) { 319 name=(String)e.nextElement(); 320 String realPassword = users.getProperty(name); 321 // Encrypt the password if needed, so we can compare it with the supplied one 322 if (realPassword != null) { 323 String pw = EncryptionManager.encrypt(realPassword); 324 if (!realPassword.equals(pw)) { 325 users.setProperty(name, pw); 326 bUpdates = true; 327 } 328 } 329 } 330 331 // rewrite the users.properties file if we had passwords to encrypt 332 if (bUpdates) 333 { 334 log.debug("Found password(s) that needed encrypting"); 335 store(users, serverInfo.resolveServer(getUsersURI()).toURL()); 336 } 337 } catch (Exception e) { 338 log.error("encryptAllPasswords failed", e); 339 throw new GeronimoSecurityException(e); 340 } 341 } 342 343 private void store(Properties props, URL url) throws Exception { 344 OutputStream out = null; 345 log.debug("Updating properties file="+url.toExternalForm()); 346 try { 347 try { 348 URLConnection con = url.openConnection(); 349 con.setDoOutput(true); 350 out = con.getOutputStream(); 351 } catch (Exception e) { 352 if ("file".equalsIgnoreCase(url.getProtocol()) && e instanceof UnknownServiceException) { 353 out = new FileOutputStream(new File(url.getFile())); 354 } else { 355 throw e; 356 } 357 } 358 props.store(out, null); 359 } finally { 360 if (out != null) { 361 try { 362 out.close(); 363 } catch (IOException ignored) { 364 // ignored 365 } 366 } 367 } 368 } 369 370 /** 371 * This method returns the message digest of a specified string. 372 * @param password The string that is to be digested 373 * @param algorithm Name of the Message Digest algorithm 374 * @param encoding Encoding to be used for digest data. Hex by default. 375 * @return encoded digest bytes 376 * @throws NoSuchAlgorithmException if the Message Digest algorithm is not available 377 */ 378 private String digestPassword(String password, String algorithm, String encoding) throws NoSuchAlgorithmException { 379 MessageDigest md = MessageDigest.getInstance(algorithm); 380 byte[] data = md.digest(password.getBytes()); 381 if(encoding == null || "hex".equalsIgnoreCase(encoding)) { 382 // Convert bytes to hex digits 383 byte[] hexData = new byte[data.length * 2]; 384 HexTranslator ht = new HexTranslator(); 385 ht.encode(data, 0, data.length, hexData, 0); 386 return new String(hexData); 387 } else if("base64".equalsIgnoreCase(encoding)) { 388 return new String(Base64.encode(data)); 389 } 390 return ""; 391 } 392 393 public void doFail() { 394 log.warn("Failed"); 395 } 396 397 public void doStart() throws Exception { 398 log.debug("Starting gbean"); 399 encryptAllPasswords(); 400 log.debug("Started gbean"); 401 } 402 403 public void doStop() throws Exception { 404 log.debug("Stopping gbean"); 405 clearAll(); 406 log.debug("Stopped gbean"); 407 } 408 409 public static final GBeanInfo GBEAN_INFO; 410 411 static { 412 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("PropertiesLoginModuleManager", PropertiesLoginModuleManager.class); 413 414 infoFactory.addOperation("addUserPrincipal", new Class[]{Hashtable.class}); 415 infoFactory.addOperation("removeUserPrincipal", new Class[]{String.class}); 416 infoFactory.addOperation("updateUserPrincipal", new Class[]{Hashtable.class}); 417 infoFactory.addOperation("getGroups"); 418 infoFactory.addOperation("getUsers"); 419 infoFactory.addOperation("refreshAll"); 420 421 infoFactory.addOperation("updateUserPrincipal", new Class[]{Hashtable.class}); 422 423 infoFactory.addOperation("getPassword", new Class[]{String.class}); 424 infoFactory.addOperation("getGroupMembers", new Class[]{String.class}); 425 infoFactory.addOperation("addGroupPrincipal", new Class[]{Hashtable.class}); 426 infoFactory.addOperation("removeGroupPrincipal", new Class[]{String.class}); 427 infoFactory.addOperation("updateGroupPrincipal", new Class[]{Hashtable.class}); 428 infoFactory.addOperation("addToGroup", new Class[]{String.class, String.class}); 429 infoFactory.addOperation("removeFromGroup", new Class[]{String.class, String.class}); 430 431 infoFactory.addReference("ServerInfo", ServerInfo.class, NameFactory.GERONIMO_SERVICE); 432 infoFactory.addReference("LoginModule", LoginModuleSettings.class, NameFactory.LOGIN_MODULE); 433 434 infoFactory.setConstructor(new String[]{"ServerInfo", "LoginModule"}); 435 436 GBEAN_INFO = infoFactory.getBeanInfo(); 437 } 438 439 public static GBeanInfo getGBeanInfo() { 440 return GBEAN_INFO; 441 } 442 443 }