Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ConfigUserService.java 25KB

12 лет назад
12 лет назад
12 лет назад
12 лет назад
12 лет назад
12 лет назад
12 лет назад
12 лет назад
12 лет назад
12 лет назад
12 лет назад
12 лет назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. /*
  2. * Copyright 2011 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.text.MessageFormat;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Collections;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.concurrent.ConcurrentHashMap;
  28. import org.eclipse.jgit.lib.StoredConfig;
  29. import org.eclipse.jgit.storage.file.FileBasedConfig;
  30. import org.eclipse.jgit.util.FS;
  31. import org.slf4j.Logger;
  32. import org.slf4j.LoggerFactory;
  33. import com.gitblit.models.TeamModel;
  34. import com.gitblit.models.UserModel;
  35. import com.gitblit.utils.ArrayUtils;
  36. import com.gitblit.utils.DeepCopier;
  37. import com.gitblit.utils.StringUtils;
  38. /**
  39. * ConfigUserService is Gitblit's default user service implementation since
  40. * version 0.8.0.
  41. *
  42. * Users and their repository memberships are stored in a git-style config file
  43. * which is cached and dynamically reloaded when modified. This file is
  44. * plain-text, human-readable, and may be edited with a text editor.
  45. *
  46. * Additionally, this format allows for expansion of the user model without
  47. * bringing in the complexity of a database.
  48. *
  49. * @author James Moger
  50. *
  51. */
  52. public class ConfigUserService implements IUserService {
  53. private static final String TEAM = "team";
  54. private static final String USER = "user";
  55. private static final String PASSWORD = "password";
  56. private static final String DISPLAYNAME = "displayName";
  57. private static final String EMAILADDRESS = "emailAddress";
  58. private static final String COOKIE = "cookie";
  59. private static final String REPOSITORY = "repository";
  60. private static final String ROLE = "role";
  61. private static final String MAILINGLIST = "mailingList";
  62. private static final String PRERECEIVE = "preReceiveScript";
  63. private static final String POSTRECEIVE = "postReceiveScript";
  64. private final File realmFile;
  65. private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);
  66. private final Map<String, UserModel> users = new ConcurrentHashMap<String, UserModel>();
  67. private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>();
  68. private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
  69. private volatile long lastModified;
  70. private volatile boolean forceReload;
  71. public ConfigUserService(File realmFile) {
  72. this.realmFile = realmFile;
  73. }
  74. /**
  75. * Setup the user service.
  76. *
  77. * @param settings
  78. * @since 0.7.0
  79. */
  80. @Override
  81. public void setup(IStoredSettings settings) {
  82. }
  83. /**
  84. * Does the user service support changes to credentials?
  85. *
  86. * @return true or false
  87. * @since 1.0.0
  88. */
  89. @Override
  90. public boolean supportsCredentialChanges() {
  91. return true;
  92. }
  93. /**
  94. * Does the user service support changes to user display name?
  95. *
  96. * @return true or false
  97. * @since 1.0.0
  98. */
  99. @Override
  100. public boolean supportsDisplayNameChanges() {
  101. return true;
  102. }
  103. /**
  104. * Does the user service support changes to user email address?
  105. *
  106. * @return true or false
  107. * @since 1.0.0
  108. */
  109. @Override
  110. public boolean supportsEmailAddressChanges() {
  111. return true;
  112. }
  113. /**
  114. * Does the user service support changes to team memberships?
  115. *
  116. * @return true or false
  117. * @since 1.0.0
  118. */
  119. public boolean supportsTeamMembershipChanges() {
  120. return true;
  121. }
  122. /**
  123. * Does the user service support cookie authentication?
  124. *
  125. * @return true or false
  126. */
  127. @Override
  128. public boolean supportsCookies() {
  129. return true;
  130. }
  131. /**
  132. * Returns the cookie value for the specified user.
  133. *
  134. * @param model
  135. * @return cookie value
  136. */
  137. @Override
  138. public String getCookie(UserModel model) {
  139. if (!StringUtils.isEmpty(model.cookie)) {
  140. return model.cookie;
  141. }
  142. read();
  143. UserModel storedModel = users.get(model.username.toLowerCase());
  144. return storedModel.cookie;
  145. }
  146. /**
  147. * Authenticate a user based on their cookie.
  148. *
  149. * @param cookie
  150. * @return a user object or null
  151. */
  152. @Override
  153. public UserModel authenticate(char[] cookie) {
  154. String hash = new String(cookie);
  155. if (StringUtils.isEmpty(hash)) {
  156. return null;
  157. }
  158. read();
  159. UserModel model = null;
  160. if (cookies.containsKey(hash)) {
  161. model = cookies.get(hash);
  162. }
  163. return model;
  164. }
  165. /**
  166. * Authenticate a user based on a username and password.
  167. *
  168. * @param username
  169. * @param password
  170. * @return a user object or null
  171. */
  172. @Override
  173. public UserModel authenticate(String username, char[] password) {
  174. read();
  175. UserModel returnedUser = null;
  176. UserModel user = getUserModel(username);
  177. if (user == null) {
  178. return null;
  179. }
  180. if (user.password.startsWith(StringUtils.MD5_TYPE)) {
  181. // password digest
  182. String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
  183. if (user.password.equalsIgnoreCase(md5)) {
  184. returnedUser = user;
  185. }
  186. } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
  187. // username+password digest
  188. String md5 = StringUtils.COMBINED_MD5_TYPE
  189. + StringUtils.getMD5(username.toLowerCase() + new String(password));
  190. if (user.password.equalsIgnoreCase(md5)) {
  191. returnedUser = user;
  192. }
  193. } else if (user.password.equals(new String(password))) {
  194. // plain-text password
  195. returnedUser = user;
  196. }
  197. return returnedUser;
  198. }
  199. /**
  200. * Logout a user.
  201. *
  202. * @param user
  203. */
  204. @Override
  205. public void logout(UserModel user) {
  206. }
  207. /**
  208. * Retrieve the user object for the specified username.
  209. *
  210. * @param username
  211. * @return a user object or null
  212. */
  213. @Override
  214. public UserModel getUserModel(String username) {
  215. read();
  216. UserModel model = users.get(username.toLowerCase());
  217. if (model != null) {
  218. // clone the model, otherwise all changes to this object are
  219. // live and unpersisted
  220. model = DeepCopier.copy(model);
  221. }
  222. return model;
  223. }
  224. /**
  225. * Updates/writes a complete user object.
  226. *
  227. * @param model
  228. * @return true if update is successful
  229. */
  230. @Override
  231. public boolean updateUserModel(UserModel model) {
  232. return updateUserModel(model.username, model);
  233. }
  234. /**
  235. * Updates/writes and replaces a complete user object keyed by username.
  236. * This method allows for renaming a user.
  237. *
  238. * @param username
  239. * the old username
  240. * @param model
  241. * the user object to use for username
  242. * @return true if update is successful
  243. */
  244. @Override
  245. public boolean updateUserModel(String username, UserModel model) {
  246. UserModel originalUser = null;
  247. try {
  248. read();
  249. originalUser = users.remove(username.toLowerCase());
  250. users.put(model.username.toLowerCase(), model);
  251. // null check on "final" teams because JSON-sourced UserModel
  252. // can have a null teams object
  253. if (model.teams != null) {
  254. for (TeamModel team : model.teams) {
  255. TeamModel t = teams.get(team.name.toLowerCase());
  256. if (t == null) {
  257. // new team
  258. team.addUser(username);
  259. teams.put(team.name.toLowerCase(), team);
  260. } else {
  261. // do not clobber existing team definition
  262. // maybe because this is a federated user
  263. t.removeUser(username);
  264. t.addUser(model.username);
  265. }
  266. }
  267. // check for implicit team removal
  268. if (originalUser != null) {
  269. for (TeamModel team : originalUser.teams) {
  270. if (!model.isTeamMember(team.name)) {
  271. team.removeUser(username);
  272. }
  273. }
  274. }
  275. }
  276. write();
  277. return true;
  278. } catch (Throwable t) {
  279. if (originalUser != null) {
  280. // restore original user
  281. users.put(originalUser.username.toLowerCase(), originalUser);
  282. } else {
  283. // drop attempted add
  284. users.remove(model.username.toLowerCase());
  285. }
  286. logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
  287. t);
  288. }
  289. return false;
  290. }
  291. /**
  292. * Deletes the user object from the user service.
  293. *
  294. * @param model
  295. * @return true if successful
  296. */
  297. @Override
  298. public boolean deleteUserModel(UserModel model) {
  299. return deleteUser(model.username);
  300. }
  301. /**
  302. * Delete the user object with the specified username
  303. *
  304. * @param username
  305. * @return true if successful
  306. */
  307. @Override
  308. public boolean deleteUser(String username) {
  309. try {
  310. // Read realm file
  311. read();
  312. UserModel model = users.remove(username.toLowerCase());
  313. // remove user from team
  314. for (TeamModel team : model.teams) {
  315. TeamModel t = teams.get(team.name);
  316. if (t == null) {
  317. // new team
  318. team.removeUser(username);
  319. teams.put(team.name.toLowerCase(), team);
  320. } else {
  321. // existing team
  322. t.removeUser(username);
  323. }
  324. }
  325. write();
  326. return true;
  327. } catch (Throwable t) {
  328. logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
  329. }
  330. return false;
  331. }
  332. /**
  333. * Returns the list of all teams available to the login service.
  334. *
  335. * @return list of all teams
  336. * @since 0.8.0
  337. */
  338. @Override
  339. public List<String> getAllTeamNames() {
  340. read();
  341. List<String> list = new ArrayList<String>(teams.keySet());
  342. Collections.sort(list);
  343. return list;
  344. }
  345. /**
  346. * Returns the list of all teams available to the login service.
  347. *
  348. * @return list of all teams
  349. * @since 0.8.0
  350. */
  351. @Override
  352. public List<TeamModel> getAllTeams() {
  353. read();
  354. List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
  355. list = DeepCopier.copy(list);
  356. Collections.sort(list);
  357. return list;
  358. }
  359. /**
  360. * Returns the list of all users who are allowed to bypass the access
  361. * restriction placed on the specified repository.
  362. *
  363. * @param role
  364. * the repository name
  365. * @return list of all usernames that can bypass the access restriction
  366. */
  367. @Override
  368. public List<String> getTeamnamesForRepositoryRole(String role) {
  369. List<String> list = new ArrayList<String>();
  370. try {
  371. read();
  372. for (Map.Entry<String, TeamModel> entry : teams.entrySet()) {
  373. TeamModel model = entry.getValue();
  374. if (model.hasRepository(role)) {
  375. list.add(model.name);
  376. }
  377. }
  378. } catch (Throwable t) {
  379. logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
  380. }
  381. Collections.sort(list);
  382. return list;
  383. }
  384. /**
  385. * Sets the list of all teams who are allowed to bypass the access
  386. * restriction placed on the specified repository.
  387. *
  388. * @param role
  389. * the repository name
  390. * @param teamnames
  391. * @return true if successful
  392. */
  393. @Override
  394. public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
  395. try {
  396. Set<String> specifiedTeams = new HashSet<String>();
  397. for (String teamname : teamnames) {
  398. specifiedTeams.add(teamname.toLowerCase());
  399. }
  400. read();
  401. // identify teams which require add or remove role
  402. for (TeamModel team : teams.values()) {
  403. // team has role, check against revised team list
  404. if (specifiedTeams.contains(team.name.toLowerCase())) {
  405. team.addRepository(role);
  406. } else {
  407. // remove role from team
  408. team.removeRepository(role);
  409. }
  410. }
  411. // persist changes
  412. write();
  413. return true;
  414. } catch (Throwable t) {
  415. logger.error(MessageFormat.format("Failed to set teams for role {0}!", role), t);
  416. }
  417. return false;
  418. }
  419. /**
  420. * Retrieve the team object for the specified team name.
  421. *
  422. * @param teamname
  423. * @return a team object or null
  424. * @since 0.8.0
  425. */
  426. @Override
  427. public TeamModel getTeamModel(String teamname) {
  428. read();
  429. TeamModel model = teams.get(teamname.toLowerCase());
  430. if (model != null) {
  431. // clone the model, otherwise all changes to this object are
  432. // live and unpersisted
  433. model = DeepCopier.copy(model);
  434. }
  435. return model;
  436. }
  437. /**
  438. * Updates/writes a complete team object.
  439. *
  440. * @param model
  441. * @return true if update is successful
  442. * @since 0.8.0
  443. */
  444. @Override
  445. public boolean updateTeamModel(TeamModel model) {
  446. return updateTeamModel(model.name, model);
  447. }
  448. /**
  449. * Updates/writes and replaces a complete team object keyed by teamname.
  450. * This method allows for renaming a team.
  451. *
  452. * @param teamname
  453. * the old teamname
  454. * @param model
  455. * the team object to use for teamname
  456. * @return true if update is successful
  457. * @since 0.8.0
  458. */
  459. @Override
  460. public boolean updateTeamModel(String teamname, TeamModel model) {
  461. TeamModel original = null;
  462. try {
  463. read();
  464. original = teams.remove(teamname.toLowerCase());
  465. teams.put(model.name.toLowerCase(), model);
  466. write();
  467. return true;
  468. } catch (Throwable t) {
  469. if (original != null) {
  470. // restore original team
  471. teams.put(original.name.toLowerCase(), original);
  472. } else {
  473. // drop attempted add
  474. teams.remove(model.name.toLowerCase());
  475. }
  476. logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
  477. }
  478. return false;
  479. }
  480. /**
  481. * Deletes the team object from the user service.
  482. *
  483. * @param model
  484. * @return true if successful
  485. * @since 0.8.0
  486. */
  487. @Override
  488. public boolean deleteTeamModel(TeamModel model) {
  489. return deleteTeam(model.name);
  490. }
  491. /**
  492. * Delete the team object with the specified teamname
  493. *
  494. * @param teamname
  495. * @return true if successful
  496. * @since 0.8.0
  497. */
  498. @Override
  499. public boolean deleteTeam(String teamname) {
  500. try {
  501. // Read realm file
  502. read();
  503. teams.remove(teamname.toLowerCase());
  504. write();
  505. return true;
  506. } catch (Throwable t) {
  507. logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
  508. }
  509. return false;
  510. }
  511. /**
  512. * Returns the list of all users available to the login service.
  513. *
  514. * @return list of all usernames
  515. */
  516. @Override
  517. public List<String> getAllUsernames() {
  518. read();
  519. List<String> list = new ArrayList<String>(users.keySet());
  520. Collections.sort(list);
  521. return list;
  522. }
  523. /**
  524. * Returns the list of all users available to the login service.
  525. *
  526. * @return list of all usernames
  527. */
  528. @Override
  529. public List<UserModel> getAllUsers() {
  530. read();
  531. List<UserModel> list = new ArrayList<UserModel>(users.values());
  532. list = DeepCopier.copy(list);
  533. Collections.sort(list);
  534. return list;
  535. }
  536. /**
  537. * Returns the list of all users who are allowed to bypass the access
  538. * restriction placed on the specified repository.
  539. *
  540. * @param role
  541. * the repository name
  542. * @return list of all usernames that can bypass the access restriction
  543. */
  544. @Override
  545. public List<String> getUsernamesForRepositoryRole(String role) {
  546. List<String> list = new ArrayList<String>();
  547. try {
  548. read();
  549. for (Map.Entry<String, UserModel> entry : users.entrySet()) {
  550. UserModel model = entry.getValue();
  551. if (model.hasRepository(role)) {
  552. list.add(model.username);
  553. }
  554. }
  555. } catch (Throwable t) {
  556. logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
  557. }
  558. Collections.sort(list);
  559. return list;
  560. }
  561. /**
  562. * Sets the list of all uses who are allowed to bypass the access
  563. * restriction placed on the specified repository.
  564. *
  565. * @param role
  566. * the repository name
  567. * @param usernames
  568. * @return true if successful
  569. */
  570. @Override
  571. public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
  572. try {
  573. Set<String> specifiedUsers = new HashSet<String>();
  574. for (String username : usernames) {
  575. specifiedUsers.add(username.toLowerCase());
  576. }
  577. read();
  578. // identify users which require add or remove role
  579. for (UserModel user : users.values()) {
  580. // user has role, check against revised user list
  581. if (specifiedUsers.contains(user.username.toLowerCase())) {
  582. user.addRepository(role);
  583. } else {
  584. // remove role from user
  585. user.removeRepository(role);
  586. }
  587. }
  588. // persist changes
  589. write();
  590. return true;
  591. } catch (Throwable t) {
  592. logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
  593. }
  594. return false;
  595. }
  596. /**
  597. * Renames a repository role.
  598. *
  599. * @param oldRole
  600. * @param newRole
  601. * @return true if successful
  602. */
  603. @Override
  604. public boolean renameRepositoryRole(String oldRole, String newRole) {
  605. try {
  606. read();
  607. // identify users which require role rename
  608. for (UserModel model : users.values()) {
  609. if (model.hasRepository(oldRole)) {
  610. model.removeRepository(oldRole);
  611. model.addRepository(newRole);
  612. }
  613. }
  614. // identify teams which require role rename
  615. for (TeamModel model : teams.values()) {
  616. if (model.hasRepository(oldRole)) {
  617. model.removeRepository(oldRole);
  618. model.addRepository(newRole);
  619. }
  620. }
  621. // persist changes
  622. write();
  623. return true;
  624. } catch (Throwable t) {
  625. logger.error(
  626. MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
  627. }
  628. return false;
  629. }
  630. /**
  631. * Removes a repository role from all users.
  632. *
  633. * @param role
  634. * @return true if successful
  635. */
  636. @Override
  637. public boolean deleteRepositoryRole(String role) {
  638. try {
  639. read();
  640. // identify users which require role rename
  641. for (UserModel user : users.values()) {
  642. user.removeRepository(role);
  643. }
  644. // identify teams which require role rename
  645. for (TeamModel team : teams.values()) {
  646. team.removeRepository(role);
  647. }
  648. // persist changes
  649. write();
  650. return true;
  651. } catch (Throwable t) {
  652. logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
  653. }
  654. return false;
  655. }
  656. /**
  657. * Writes the properties file.
  658. *
  659. * @param properties
  660. * @throws IOException
  661. */
  662. private synchronized void write() throws IOException {
  663. // Write a temporary copy of the users file
  664. File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
  665. StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect());
  666. // write users
  667. for (UserModel model : users.values()) {
  668. if (!StringUtils.isEmpty(model.password)) {
  669. config.setString(USER, model.username, PASSWORD, model.password);
  670. }
  671. if (!StringUtils.isEmpty(model.cookie)) {
  672. config.setString(USER, model.username, COOKIE, model.cookie);
  673. }
  674. if (!StringUtils.isEmpty(model.displayName)) {
  675. config.setString(USER, model.username, DISPLAYNAME, model.displayName);
  676. }
  677. if (!StringUtils.isEmpty(model.emailAddress)) {
  678. config.setString(USER, model.username, EMAILADDRESS, model.emailAddress);
  679. }
  680. // user roles
  681. List<String> roles = new ArrayList<String>();
  682. if (model.canAdmin) {
  683. roles.add(Constants.ADMIN_ROLE);
  684. }
  685. if (model.canFork) {
  686. roles.add(Constants.FORK_ROLE);
  687. }
  688. if (model.excludeFromFederation) {
  689. roles.add(Constants.NOT_FEDERATED_ROLE);
  690. }
  691. if (roles.size() == 0) {
  692. // we do this to ensure that user record with no password
  693. // is written. otherwise, StoredConfig optimizes that account
  694. // away. :(
  695. roles.add(Constants.NO_ROLE);
  696. }
  697. config.setStringList(USER, model.username, ROLE, roles);
  698. // repository memberships
  699. // null check on "final" repositories because JSON-sourced UserModel
  700. // can have a null repositories object
  701. if (!ArrayUtils.isEmpty(model.repositories)) {
  702. config.setStringList(USER, model.username, REPOSITORY, new ArrayList<String>(
  703. model.repositories));
  704. }
  705. }
  706. // write teams
  707. for (TeamModel model : teams.values()) {
  708. // null check on "final" repositories because JSON-sourced TeamModel
  709. // can have a null repositories object
  710. if (!ArrayUtils.isEmpty(model.repositories)) {
  711. config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(
  712. model.repositories));
  713. }
  714. // null check on "final" users because JSON-sourced TeamModel
  715. // can have a null users object
  716. if (!ArrayUtils.isEmpty(model.users)) {
  717. config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users));
  718. }
  719. // null check on "final" mailing lists because JSON-sourced
  720. // TeamModel can have a null users object
  721. if (!ArrayUtils.isEmpty(model.mailingLists)) {
  722. config.setStringList(TEAM, model.name, MAILINGLIST, new ArrayList<String>(
  723. model.mailingLists));
  724. }
  725. // null check on "final" preReceiveScripts because JSON-sourced
  726. // TeamModel can have a null preReceiveScripts object
  727. if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
  728. config.setStringList(TEAM, model.name, PRERECEIVE, model.preReceiveScripts);
  729. }
  730. // null check on "final" postReceiveScripts because JSON-sourced
  731. // TeamModel can have a null postReceiveScripts object
  732. if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
  733. config.setStringList(TEAM, model.name, POSTRECEIVE, model.postReceiveScripts);
  734. }
  735. }
  736. config.save();
  737. // manually set the forceReload flag because not all JVMs support real
  738. // millisecond resolution of lastModified. (issue-55)
  739. forceReload = true;
  740. // If the write is successful, delete the current file and rename
  741. // the temporary copy to the original filename.
  742. if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
  743. if (realmFile.exists()) {
  744. if (!realmFile.delete()) {
  745. throw new IOException(MessageFormat.format("Failed to delete {0}!",
  746. realmFile.getAbsolutePath()));
  747. }
  748. }
  749. if (!realmFileCopy.renameTo(realmFile)) {
  750. throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
  751. realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath()));
  752. }
  753. } else {
  754. throw new IOException(MessageFormat.format("Failed to save {0}!",
  755. realmFileCopy.getAbsolutePath()));
  756. }
  757. }
  758. /**
  759. * Reads the realm file and rebuilds the in-memory lookup tables.
  760. */
  761. protected synchronized void read() {
  762. if (realmFile.exists() && (forceReload || (realmFile.lastModified() != lastModified))) {
  763. forceReload = false;
  764. lastModified = realmFile.lastModified();
  765. users.clear();
  766. cookies.clear();
  767. teams.clear();
  768. try {
  769. StoredConfig config = new FileBasedConfig(realmFile, FS.detect());
  770. config.load();
  771. Set<String> usernames = config.getSubsections(USER);
  772. for (String username : usernames) {
  773. UserModel user = new UserModel(username.toLowerCase());
  774. user.password = config.getString(USER, username, PASSWORD);
  775. user.displayName = config.getString(USER, username, DISPLAYNAME);
  776. user.emailAddress = config.getString(USER, username, EMAILADDRESS);
  777. user.cookie = config.getString(USER, username, COOKIE);
  778. if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {
  779. user.cookie = StringUtils.getSHA1(user.username + user.password);
  780. }
  781. // user roles
  782. Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
  783. USER, username, ROLE)));
  784. user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
  785. user.canFork = roles.contains(Constants.FORK_ROLE);
  786. user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
  787. // repository memberships
  788. Set<String> repositories = new HashSet<String>(Arrays.asList(config
  789. .getStringList(USER, username, REPOSITORY)));
  790. for (String repository : repositories) {
  791. user.addRepository(repository);
  792. }
  793. // update cache
  794. users.put(user.username, user);
  795. if (!StringUtils.isEmpty(user.cookie)) {
  796. cookies.put(user.cookie, user);
  797. }
  798. }
  799. // load the teams
  800. Set<String> teamnames = config.getSubsections(TEAM);
  801. for (String teamname : teamnames) {
  802. TeamModel team = new TeamModel(teamname);
  803. team.addRepositories(Arrays.asList(config.getStringList(TEAM, teamname,
  804. REPOSITORY)));
  805. team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));
  806. team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname,
  807. MAILINGLIST)));
  808. team.preReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
  809. teamname, PRERECEIVE)));
  810. team.postReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
  811. teamname, POSTRECEIVE)));
  812. teams.put(team.name.toLowerCase(), team);
  813. // set the teams on the users
  814. for (String user : team.users) {
  815. UserModel model = users.get(user);
  816. if (model != null) {
  817. model.teams.add(team);
  818. }
  819. }
  820. }
  821. } catch (Exception e) {
  822. logger.error(MessageFormat.format("Failed to read {0}", realmFile), e);
  823. }
  824. }
  825. }
  826. protected long lastModified() {
  827. return lastModified;
  828. }
  829. @Override
  830. public String toString() {
  831. return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")";
  832. }
  833. }