PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/qpid-0.16/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java

#
Java | 484 lines | 368 code | 65 blank | 51 comment | 52 complexity | 5ad356fa0fc05bf2da9d9dc2c09be1fd MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1
  1. /*
  2. *
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. *
  20. */
  21. package org.apache.qpid.server.security.auth.database;
  22. import org.apache.log4j.Logger;
  23. import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
  24. import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser;
  25. import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
  26. import javax.security.auth.callback.PasswordCallback;
  27. import javax.security.auth.login.AccountNotFoundException;
  28. import java.io.BufferedReader;
  29. import java.io.File;
  30. import java.io.FileNotFoundException;
  31. import java.io.FileReader;
  32. import java.io.IOException;
  33. import java.io.PrintStream;
  34. import java.security.Principal;
  35. import java.util.HashMap;
  36. import java.util.LinkedList;
  37. import java.util.List;
  38. import java.util.Map;
  39. import java.util.Random;
  40. import java.util.concurrent.locks.ReentrantLock;
  41. import java.util.regex.Pattern;
  42. public abstract class AbstractPasswordFilePrincipalDatabase<U extends PasswordPrincipal> implements PrincipalDatabase
  43. {
  44. private final Pattern _regexp = Pattern.compile(":");
  45. private final Map<String, AuthenticationProviderInitialiser> _saslServers =
  46. new HashMap<String, AuthenticationProviderInitialiser>();
  47. protected static final String DEFAULT_ENCODING = "utf-8";
  48. private final Map<String, U> _userMap = new HashMap<String, U>();
  49. private final ReentrantLock _userUpdate = new ReentrantLock();
  50. private final Random _random = new Random();
  51. private File _passwordFile;
  52. protected AbstractPasswordFilePrincipalDatabase(UsernamePasswordInitialiser... initialisers)
  53. {
  54. for(UsernamePasswordInitialiser initialiser : initialisers)
  55. {
  56. initialiser.initialise(this);
  57. _saslServers.put(initialiser.getMechanismName(), initialiser);
  58. }
  59. }
  60. public final void setPasswordFile(String passwordFile) throws IOException
  61. {
  62. File f = new File(passwordFile);
  63. getLogger().info("PasswordFile using file " + f.getAbsolutePath());
  64. _passwordFile = f;
  65. if (!f.exists())
  66. {
  67. throw new FileNotFoundException("Cannot find password file " + f);
  68. }
  69. if (!f.canRead())
  70. {
  71. throw new FileNotFoundException("Cannot read password file " + f +
  72. ". Check permissions.");
  73. }
  74. loadPasswordFile();
  75. }
  76. /**
  77. * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile
  78. * If you want to change the password for a user, use updatePassword instead.
  79. *
  80. * @param principal The Principal to set the password for
  81. * @param callback The PasswordCallback to call setPassword on
  82. *
  83. * @throws javax.security.auth.login.AccountNotFoundException If the Principal cannot be found in this Database
  84. */
  85. public final void setPassword(Principal principal, PasswordCallback callback) throws AccountNotFoundException
  86. {
  87. if (_passwordFile == null)
  88. {
  89. throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation");
  90. }
  91. if (principal == null)
  92. {
  93. throw new IllegalArgumentException("principal must not be null");
  94. }
  95. char[] pwd = lookupPassword(principal.getName());
  96. if (pwd != null)
  97. {
  98. callback.setPassword(pwd);
  99. }
  100. else
  101. {
  102. throw new AccountNotFoundException("No account found for principal " + principal);
  103. }
  104. }
  105. /**
  106. * Looks up the password for a specified user in the password file. Note this code is <b>not</b> secure since it
  107. * creates strings of passwords. It should be modified to create only char arrays which get nulled out.
  108. *
  109. * @param name The principal name to lookup
  110. *
  111. * @return a char[] for use in SASL.
  112. */
  113. protected final char[] lookupPassword(String name)
  114. {
  115. U user = _userMap.get(name);
  116. if (user == null)
  117. {
  118. return null;
  119. }
  120. else
  121. {
  122. return user.getPassword();
  123. }
  124. }
  125. protected boolean compareCharArray(char[] a, char[] b)
  126. {
  127. boolean equal = false;
  128. if (a.length == b.length)
  129. {
  130. equal = true;
  131. int index = 0;
  132. while (equal && index < a.length)
  133. {
  134. equal = a[index] == b[index];
  135. index++;
  136. }
  137. }
  138. return equal;
  139. }
  140. /**
  141. * Changes the password for the specified user
  142. *
  143. * @param principal to change the password for
  144. * @param password plaintext password to set the password too
  145. */
  146. public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException
  147. {
  148. U user = _userMap.get(principal.getName());
  149. if (user == null)
  150. {
  151. throw new AccountNotFoundException(principal.getName());
  152. }
  153. char[] orig = user.getPassword();
  154. _userUpdate.lock();
  155. try
  156. {
  157. user.setPassword(password);
  158. savePasswordFile();
  159. return true;
  160. }
  161. catch (IOException e)
  162. {
  163. getLogger().error("Unable to save password file due to '" + e.getMessage()
  164. + "', password change for user '" + principal + "' discarded");
  165. //revert the password change
  166. user.restorePassword(orig);
  167. return false;
  168. }
  169. finally
  170. {
  171. _userUpdate.unlock();
  172. }
  173. }
  174. private void loadPasswordFile() throws IOException
  175. {
  176. try
  177. {
  178. _userUpdate.lock();
  179. _userMap.clear();
  180. BufferedReader reader = null;
  181. try
  182. {
  183. reader = new BufferedReader(new FileReader(_passwordFile));
  184. String line;
  185. while ((line = reader.readLine()) != null)
  186. {
  187. String[] result = _regexp.split(line);
  188. if (result == null || result.length < 2 || result[0].startsWith("#"))
  189. {
  190. continue;
  191. }
  192. U user = createUserFromFileData(result);
  193. getLogger().info("Created user:" + user);
  194. _userMap.put(user.getName(), user);
  195. }
  196. }
  197. finally
  198. {
  199. if (reader != null)
  200. {
  201. reader.close();
  202. }
  203. }
  204. }
  205. finally
  206. {
  207. _userUpdate.unlock();
  208. }
  209. }
  210. protected abstract U createUserFromFileData(String[] result);
  211. protected abstract Logger getLogger();
  212. protected File createTempFileOnSameFilesystem()
  213. {
  214. File liveFile = _passwordFile;
  215. File tmp;
  216. do
  217. {
  218. tmp = new File(liveFile.getPath() + _random.nextInt() + ".tmp");
  219. }
  220. while(tmp.exists());
  221. tmp.deleteOnExit();
  222. return tmp;
  223. }
  224. protected void swapTempFileToLive(final File temp) throws IOException
  225. {
  226. File live = _passwordFile;
  227. // Remove any existing ".old" file
  228. final File old = new File(live.getAbsoluteFile() + ".old");
  229. if (old.exists())
  230. {
  231. old.delete();
  232. }
  233. // Create an new ".old" file
  234. if(!live.renameTo(old))
  235. {
  236. //unable to rename the existing file to the backup name
  237. getLogger().error("Could not backup the existing password file");
  238. throw new IOException("Could not backup the existing password file");
  239. }
  240. // Move temp file to be the new "live" file
  241. if(!temp.renameTo(live))
  242. {
  243. //failed to rename the new file to the required filename
  244. if(!old.renameTo(live))
  245. {
  246. //unable to return the backup to required filename
  247. getLogger().error(
  248. "Could not rename the new password file into place, and unable to restore original file");
  249. throw new IOException("Could not rename the new password file into place, and unable to restore original file");
  250. }
  251. getLogger().error("Could not rename the new password file into place");
  252. throw new IOException("Could not rename the new password file into place");
  253. }
  254. }
  255. protected void savePasswordFile() throws IOException
  256. {
  257. try
  258. {
  259. _userUpdate.lock();
  260. BufferedReader reader = null;
  261. PrintStream writer = null;
  262. File tmp = createTempFileOnSameFilesystem();
  263. try
  264. {
  265. writer = new PrintStream(tmp);
  266. reader = new BufferedReader(new FileReader(_passwordFile));
  267. String line;
  268. while ((line = reader.readLine()) != null)
  269. {
  270. String[] result = _regexp.split(line);
  271. if (result == null || result.length < 2 || result[0].startsWith("#"))
  272. {
  273. writer.write(line.getBytes(DEFAULT_ENCODING));
  274. writer.println();
  275. continue;
  276. }
  277. U user = _userMap.get(result[0]);
  278. if (user == null)
  279. {
  280. writer.write(line.getBytes(DEFAULT_ENCODING));
  281. writer.println();
  282. }
  283. else if (!user.isDeleted())
  284. {
  285. if (!user.isModified())
  286. {
  287. writer.write(line.getBytes(DEFAULT_ENCODING));
  288. writer.println();
  289. }
  290. else
  291. {
  292. byte[] encodedPassword = user.getEncodedPassword();
  293. writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING));
  294. writer.write(encodedPassword);
  295. writer.println();
  296. user.saved();
  297. }
  298. }
  299. }
  300. for (U user : _userMap.values())
  301. {
  302. if (user.isModified())
  303. {
  304. byte[] encodedPassword;
  305. encodedPassword = user.getEncodedPassword();
  306. writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING));
  307. writer.write(encodedPassword);
  308. writer.println();
  309. user.saved();
  310. }
  311. }
  312. }
  313. catch(IOException e)
  314. {
  315. getLogger().error("Unable to create the new password file: " + e);
  316. throw new IOException("Unable to create the new password file",e);
  317. }
  318. finally
  319. {
  320. try
  321. {
  322. if (reader != null)
  323. {
  324. reader.close();
  325. }
  326. }
  327. finally
  328. {
  329. if (writer != null)
  330. {
  331. writer.close();
  332. }
  333. }
  334. }
  335. swapTempFileToLive(tmp);
  336. }
  337. finally
  338. {
  339. _userUpdate.unlock();
  340. }
  341. }
  342. protected abstract U createUserFromPassword(Principal principal, char[] passwd);
  343. public void reload() throws IOException
  344. {
  345. loadPasswordFile();
  346. }
  347. public Map<String, AuthenticationProviderInitialiser> getMechanisms()
  348. {
  349. return _saslServers;
  350. }
  351. public List<Principal> getUsers()
  352. {
  353. return new LinkedList<Principal>(_userMap.values());
  354. }
  355. public Principal getUser(String username)
  356. {
  357. if (_userMap.containsKey(username))
  358. {
  359. return new UsernamePrincipal(username);
  360. }
  361. return null;
  362. }
  363. public boolean deletePrincipal(Principal principal) throws AccountNotFoundException
  364. {
  365. U user = _userMap.get(principal.getName());
  366. if (user == null)
  367. {
  368. throw new AccountNotFoundException(principal.getName());
  369. }
  370. try
  371. {
  372. _userUpdate.lock();
  373. user.delete();
  374. try
  375. {
  376. savePasswordFile();
  377. }
  378. catch (IOException e)
  379. {
  380. getLogger().error("Unable to remove user '" + user.getName() + "' from password file.");
  381. return false;
  382. }
  383. _userMap.remove(user.getName());
  384. }
  385. finally
  386. {
  387. _userUpdate.unlock();
  388. }
  389. return true;
  390. }
  391. public boolean createPrincipal(Principal principal, char[] password)
  392. {
  393. if (_userMap.get(principal.getName()) != null)
  394. {
  395. return false;
  396. }
  397. U user = createUserFromPassword(principal, password);
  398. try
  399. {
  400. _userUpdate.lock();
  401. _userMap.put(user.getName(), user);
  402. try
  403. {
  404. savePasswordFile();
  405. return true;
  406. }
  407. catch (IOException e)
  408. {
  409. //remove the use on failure.
  410. _userMap.remove(user.getName());
  411. return false;
  412. }
  413. }
  414. finally
  415. {
  416. _userUpdate.unlock();
  417. }
  418. }
  419. }