You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ConfigUserService.java 25KB

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. }